aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorST-DDT <[email protected]>2024-04-01 10:21:18 +0200
committerGitHub <[email protected]>2024-04-01 10:21:18 +0200
commit6191a5d883048b694404dbf42527caba395828ea (patch)
treed0f18f17789cb0bbdb5d6087f1a95772438dfe27
parent7dae52bfcd93c41ec9d2c4dd4d96a07f31c3dfc1 (diff)
downloadfaker-6191a5d883048b694404dbf42527caba395828ea.tar.xz
faker-6191a5d883048b694404dbf42527caba395828ea.zip
docs: rewrite api-docs generation using ts-morph (#2628)
-rw-r--r--.github/renovate.json57
-rw-r--r--.prettierignore2
-rw-r--r--docs/.vitepress/components/api-docs/method-parameters.vue4
-rw-r--r--docs/.vitepress/components/api-docs/method.ts18
-rw-r--r--docs/.vitepress/components/api-docs/method.vue4
-rw-r--r--package.json9
-rw-r--r--pnpm-lock.yaml81
-rw-r--r--scripts/apidoc.ts7
-rw-r--r--scripts/apidoc/faker-class.ts86
-rw-r--r--scripts/apidoc/faker-utilities.ts40
-rw-r--r--scripts/apidoc/generate.ts42
-rw-r--r--scripts/apidoc/module-methods.ts122
-rw-r--r--scripts/apidoc/parameter-defaults.ts136
-rw-r--r--scripts/apidoc/signature.ts365
-rw-r--r--scripts/apidoc/typedoc.ts421
-rw-r--r--scripts/apidoc/utils.ts79
-rw-r--r--scripts/apidoc/writer.ts249
-rw-r--r--scripts/apidocs.ts7
-rw-r--r--scripts/apidocs/diff.ts (renamed from scripts/apidoc/diff.ts)21
-rw-r--r--scripts/apidocs/generate.ts45
-rw-r--r--scripts/apidocs/output/constants.ts1
-rw-r--r--scripts/apidocs/output/diff-index.ts81
-rw-r--r--scripts/apidocs/output/page-index.ts38
-rw-r--r--scripts/apidocs/output/page.ts172
-rw-r--r--scripts/apidocs/output/search-index.ts34
-rw-r--r--scripts/apidocs/output/source-base-url.ts38
-rw-r--r--scripts/apidocs/processing/class.ts223
-rw-r--r--scripts/apidocs/processing/error.ts40
-rw-r--r--scripts/apidocs/processing/jsdocs.ts91
-rw-r--r--scripts/apidocs/processing/method.ts197
-rw-r--r--scripts/apidocs/processing/parameter.ts203
-rw-r--r--scripts/apidocs/processing/signature.ts158
-rw-r--r--scripts/apidocs/processing/source.ts37
-rw-r--r--scripts/apidocs/processing/type.ts231
-rw-r--r--scripts/apidocs/project.ts9
-rw-r--r--scripts/apidocs/utils/format.ts (renamed from scripts/apidoc/format.ts)2
-rw-r--r--scripts/apidocs/utils/markdown.ts (renamed from scripts/apidoc/markdown.ts)44
-rw-r--r--scripts/apidocs/utils/paths.ts24
-rw-r--r--scripts/apidocs/utils/value-checks.ts69
-rw-r--r--scripts/diff.ts8
-rw-r--r--scripts/generate-locales.ts2
-rw-r--r--test/scripts/apidoc/__snapshots__/module.spec.ts.snap54
-rw-r--r--test/scripts/apidoc/__snapshots__/signature.spec.ts.snap768
-rw-r--r--test/scripts/apidoc/module.spec.ts26
-rw-r--r--test/scripts/apidoc/signature.debug.ts17
-rw-r--r--test/scripts/apidoc/signature.spec.ts26
-rw-r--r--test/scripts/apidoc/utils.ts66
-rw-r--r--test/scripts/apidoc/verify-jsdoc-tags.spec.ts315
-rw-r--r--test/scripts/apidocs/.gitignore (renamed from test/scripts/apidoc/.gitignore)0
-rw-r--r--test/scripts/apidocs/__snapshots__/class.spec.ts.snap75
-rw-r--r--test/scripts/apidocs/__snapshots__/method.spec.ts.snap1671
-rw-r--r--test/scripts/apidocs/class.example.ts (renamed from test/scripts/apidoc/module.example.ts)0
-rw-r--r--test/scripts/apidocs/class.spec.ts22
-rw-r--r--test/scripts/apidocs/method.example.ts (renamed from test/scripts/apidoc/signature.example.ts)96
-rw-r--r--test/scripts/apidocs/method.spec.ts30
-rw-r--r--test/scripts/apidocs/utils.ts38
-rw-r--r--test/scripts/apidocs/verify-jsdoc-tags.spec.ts286
-rw-r--r--tsconfig.json2
58 files changed, 4012 insertions, 2927 deletions
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 3421abe7..ac590676 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -55,12 +55,7 @@
},
{
"groupName": "doc-dependencies",
- "matchPackageNames": [
- "@algolia/client-search",
- "typedoc",
- "typedoc-plugin-missing-exports",
- "vitepress"
- ]
+ "matchPackageNames": ["@algolia/client-search", "ts-morph", "vitepress"]
}
],
"vulnerabilityAlerts": {
diff --git a/.prettierignore b/.prettierignore
index 8997b865..9c80acc2 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,7 +1,7 @@
.pnpm-store/
coverage/
dist/
-test/scripts/apidoc/temp/
+test/scripts/apidocs/temp/
CHANGELOG.md
CHANGELOG_old.md
pnpm-lock.yaml
diff --git a/docs/.vitepress/components/api-docs/method-parameters.vue b/docs/.vitepress/components/api-docs/method-parameters.vue
index fbc28635..7b14133d 100644
--- a/docs/.vitepress/components/api-docs/method-parameters.vue
+++ b/docs/.vitepress/components/api-docs/method-parameters.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
-import type { MethodParameter } from './method';
+import type { ApiDocsMethodParameter } from './method';
-const props = defineProps<{ parameters: MethodParameter[] }>();
+const props = defineProps<{ parameters: ApiDocsMethodParameter[] }>();
</script>
<template>
diff --git a/docs/.vitepress/components/api-docs/method.ts b/docs/.vitepress/components/api-docs/method.ts
index 3c1b9b95..90310ed0 100644
--- a/docs/.vitepress/components/api-docs/method.ts
+++ b/docs/.vitepress/components/api-docs/method.ts
@@ -1,19 +1,19 @@
-export interface Method {
+export interface ApiDocsMethod {
readonly name: string;
+ readonly deprecated: string | undefined; // HTML
readonly description: string; // HTML
- readonly parameters: MethodParameter[];
+ readonly since: string;
+ readonly parameters: ApiDocsMethodParameter[];
readonly returns: string;
+ readonly throws: string | undefined; // HTML
readonly examples: string; // HTML
- readonly deprecated?: string; // HTML
- readonly since: string;
- readonly sourcePath: string; // URL-Suffix
readonly seeAlsos: string[];
- readonly throws?: string; // HTML
+ readonly sourcePath: string; // URL-Suffix
}
-export interface MethodParameter {
+export interface ApiDocsMethodParameter {
readonly name: string;
- readonly type?: string;
- readonly default?: string;
+ readonly type: string | undefined;
+ readonly default: string | undefined;
readonly description: string; // HTML
}
diff --git a/docs/.vitepress/components/api-docs/method.vue b/docs/.vitepress/components/api-docs/method.vue
index 2b3f0c5b..8e25e8f8 100644
--- a/docs/.vitepress/components/api-docs/method.vue
+++ b/docs/.vitepress/components/api-docs/method.vue
@@ -1,10 +1,10 @@
<script setup lang="ts">
-import type { Method } from './method';
+import type { ApiDocsMethod } from './method';
import MethodParameters from './method-parameters.vue';
import { slugify } from '../../shared/utils/slugify';
import { sourceBaseUrl } from '../../../api/source-base-url';
-const props = defineProps<{ method: Method }>();
+const props = defineProps<{ method: ApiDocsMethod }>();
function seeAlsoToUrl(see: string): string {
const [, module, method] = see.replace(/\(.*/, '').split('\.');
diff --git a/package.json b/package.json
index 8abe25a1..05770786 100644
--- a/package.json
+++ b/package.json
@@ -65,13 +65,12 @@
"build:types": "tsc --project tsconfig.build.json",
"build": "run-s build:clean build:code build:types",
"generate": "run-s generate:locales generate:api-docs",
- "generate:api-docs": "tsx ./scripts/apidoc.ts",
+ "generate:api-docs": "tsx ./scripts/apidocs.ts",
"generate:locales": "tsx ./scripts/generate-locales.ts",
- "docs:build": "run-s docs:prepare docs:build:run",
+ "docs:build": "run-s generate:api-docs docs:build:run",
"docs:build:run": "vitepress build docs",
"docs:build:ci": "run-s build docs:build",
- "docs:prepare": "run-s generate:api-docs",
- "docs:dev": "run-s docs:prepare docs:dev:run",
+ "docs:dev": "run-s generate:api-docs docs:dev:run",
"docs:dev:run": "vitepress dev docs",
"docs:serve": "vitepress serve docs --port 5173",
"docs:diff": "tsx ./scripts/diff.ts",
@@ -126,9 +125,9 @@
"sanitize-html": "2.13.0",
"semver": "7.6.0",
"standard-version": "9.5.0",
+ "ts-morph": "22.0.0",
"tsup": "8.0.2",
"tsx": "4.7.1",
- "typedoc": "0.25.12",
"typescript": "5.4.3",
"validator": "13.11.0",
"vite": "5.2.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 40b2565e..c79ae455 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -113,15 +113,15 @@ devDependencies:
standard-version:
specifier: 9.5.0
version: 9.5.0
+ ts-morph:
+ specifier: 22.0.0
+ version: 22.0.0
tsup:
specifier: 8.0.2
version: 8.0.2([email protected])
tsx:
specifier: 4.7.1
version: 4.7.1
- typedoc:
- specifier: 0.25.12
- version: 0.25.12([email protected])
typescript:
specifier: 5.4.3
version: 5.4.3
@@ -1297,6 +1297,15 @@ packages:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
+ /@ts-morph/[email protected]:
+ resolution: {integrity: sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==}
+ dependencies:
+ fast-glob: 3.3.2
+ minimatch: 9.0.3
+ mkdirp: 3.0.1
+ path-browserify: 1.0.1
+ dev: true
+
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
@@ -1986,10 +1995,6 @@ packages:
engines: {node: '>=12'}
dev: true
- resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==}
- dev: true
-
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
@@ -2383,6 +2388,10 @@ packages:
wrap-ansi: 7.0.0
dev: true
+ resolution: {integrity: sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==}
+ dev: true
+
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
@@ -4689,10 +4698,6 @@ packages:
yallist: 4.0.0
dev: true
- resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
- dev: true
-
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
engines: {node: '>=12'}
@@ -4729,12 +4734,6 @@ packages:
resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
dev: true
- resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
- engines: {node: '>= 12'}
- hasBin: true
- dev: true
-
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
engines: {node: '>= 0.10.0'}
@@ -4845,6 +4844,12 @@ packages:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
dev: true
+ resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dev: true
+
resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==}
dependencies:
@@ -5152,6 +5157,10 @@ packages:
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
dev: true
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+ dev: true
+
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
@@ -5738,15 +5747,6 @@ packages:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
dev: true
- resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==}
- dependencies:
- ansi-sequence-parser: 1.1.1
- jsonc-parser: 3.2.1
- vscode-oniguruma: 1.7.0
- vscode-textmate: 8.0.0
- dev: true
-
resolution: {integrity: sha512-u+XW6o0vCkUNlneZb914dLO+AayEIwK5tI62WeS//R5HIXBFiYaj/Hc5xcq27Yh83Grr4JbNtUBV8W6zyK4hWg==}
dependencies:
@@ -6226,6 +6226,13 @@ packages:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
+ resolution: {integrity: sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==}
+ dependencies:
+ '@ts-morph/common': 0.23.0
+ code-block-writer: 13.0.1
+ dev: true
+
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
@@ -6404,20 +6411,6 @@ packages:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
dev: true
- resolution: {integrity: sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==}
- engines: {node: '>= 16'}
- hasBin: true
- peerDependencies:
- typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x
- dependencies:
- lunr: 2.3.9
- marked: 4.3.0
- minimatch: 9.0.3
- shiki: 0.14.7
- typescript: 5.4.3
- dev: true
-
resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==}
engines: {node: '>=14.17'}
@@ -6707,14 +6700,6 @@ packages:
- terser
dev: true
- resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
- dev: true
-
- resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==}
- dev: true
-
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'}
diff --git a/scripts/apidoc.ts b/scripts/apidoc.ts
deleted file mode 100644
index 42eee5c3..00000000
--- a/scripts/apidoc.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env node
-
-import { generate } from './apidoc/generate';
-import { initMarkdownRenderer } from './apidoc/markdown';
-
-await initMarkdownRenderer();
-await generate();
diff --git a/scripts/apidoc/faker-class.ts b/scripts/apidoc/faker-class.ts
deleted file mode 100644
index a9acad3c..00000000
--- a/scripts/apidoc/faker-class.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import type { DeclarationReflection, ProjectReflection } from 'typedoc';
-import { ReflectionKind } from 'typedoc';
-import type { Method } from '../../docs/.vitepress/components/api-docs/method';
-import { analyzeModule, processModuleMethods } from './module-methods';
-import { analyzeSignature } from './signature';
-import { extractModuleFieldName, selectApiSignature } from './typedoc';
-import type { ModuleSummary } from './utils';
-import { writeApiDocsModule } from './writer';
-
-export async function processFakerClasses(
- project: ProjectReflection
-): Promise<ModuleSummary[]> {
- const fakerClasses = project
- .getChildrenByKind(ReflectionKind.Class)
- .filter((clazz) => clazz.name === 'Faker' || clazz.name === 'SimpleFaker');
-
- if (fakerClasses.length !== 2) {
- throw new Error('Faker classes not found');
- }
-
- return Promise.all(fakerClasses.map(processClass));
-}
-
-export async function processFakerRandomizer(
- project: ProjectReflection
-): Promise<ModuleSummary> {
- const randomizerClass = project
- .getChildrenByKind(ReflectionKind.Interface)
- .find((clazz) => clazz.name === 'Randomizer');
-
- if (randomizerClass == null) {
- throw new Error('Randomizer class not found');
- }
-
- return processClass(randomizerClass);
-}
-
-async function processClass(
- clazz: DeclarationReflection
-): Promise<ModuleSummary> {
- const { name } = clazz;
- const moduleFieldName = extractModuleFieldName(clazz);
-
- console.log(`Processing ${name} class`);
-
- const { comment, deprecated, examples } = analyzeModule(clazz);
- const methods: Method[] = [];
-
- if (hasConstructor(clazz)) {
- console.debug(`- constructor`);
- methods.push(await processConstructor(clazz));
- }
-
- methods.push(...(await processModuleMethods(clazz, `${moduleFieldName}.`)));
-
- return writeApiDocsModule(
- name,
- moduleFieldName,
- comment,
- examples,
- deprecated,
- methods,
- ''
- );
-}
-
-function hasConstructor(clazz: DeclarationReflection): boolean {
- return clazz
- .getChildrenByKind(ReflectionKind.Constructor)
- .some((constructor) => (constructor.signatures?.length ?? 0) > 0);
-}
-
-async function processConstructor(
- clazz: DeclarationReflection
-): Promise<Method> {
- const constructor = clazz.getChildrenByKind(ReflectionKind.Constructor)[0];
-
- const signature = selectApiSignature(constructor);
-
- const method = await analyzeSignature(signature, '', `new ${clazz.name}`);
-
- return {
- ...method,
- name: 'constructor',
- };
-}
diff --git a/scripts/apidoc/faker-utilities.ts b/scripts/apidoc/faker-utilities.ts
deleted file mode 100644
index ad1f74c3..00000000
--- a/scripts/apidoc/faker-utilities.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { DeclarationReflection, ProjectReflection } from 'typedoc';
-import { ReflectionKind } from 'typedoc';
-import type { Method } from '../../docs/.vitepress/components/api-docs/method';
-import { processMethods } from './module-methods';
-import { selectApiSignature } from './typedoc';
-import type { ModuleSummary } from './utils';
-import { writeApiDocsModule } from './writer';
-
-export async function processFakerUtilities(
- project: ProjectReflection
-): Promise<ModuleSummary> {
- const fakerUtilities = project
- .getChildrenByKind(ReflectionKind.Function)
- .filter((method) => !method.flags.isPrivate);
-
- return processUtilities(fakerUtilities);
-}
-
-async function processUtilities(
- fakerUtilities: DeclarationReflection[]
-): Promise<ModuleSummary> {
- console.log(`Processing Faker Utilities`);
- const comment = 'A list of all the utilities available in Faker.js.';
-
- const methods: Method[] = await processMethods(
- Object.fromEntries(
- fakerUtilities.map((method) => [method.name, selectApiSignature(method)])
- )
- );
-
- return writeApiDocsModule(
- 'Utilities',
- 'utils',
- comment,
- undefined,
- undefined,
- methods,
- ''
- );
-}
diff --git a/scripts/apidoc/generate.ts b/scripts/apidoc/generate.ts
deleted file mode 100644
index eb00cea4..00000000
--- a/scripts/apidoc/generate.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { resolve } from 'node:path';
-import { processFakerClasses, processFakerRandomizer } from './faker-class';
-import { processFakerUtilities } from './faker-utilities';
-import { processModules } from './module-methods';
-import { loadProject } from './typedoc';
-import { pathOutputDir } from './utils';
-import {
- writeApiDiffIndex,
- writeApiPagesIndex,
- writeApiSearchIndex,
- writeSourceBaseUrl,
-} from './writer';
-
-const pathOutputJson = resolve(pathOutputDir, 'typedoc.json');
-
-/**
- * Generates the API documentation.
- */
-export async function generate(): Promise<void> {
- const [app, project] = await loadProject();
-
- // Useful for manually analyzing the content
- await app.generateJson(project, pathOutputJson);
-
- const pages = [
- ...(await processFakerClasses(project)),
- await processFakerRandomizer(project),
- await processFakerUtilities(project),
- ...(await processModules(project)).sort((a, b) =>
- a.text.localeCompare(b.text)
- ),
- ];
- await writeApiPagesIndex(
- pages.map(({ text, link, category }) => ({ text, link, category }))
- );
- writeApiDiffIndex(
- Object.fromEntries(pages.map(({ text, diff }) => [text, diff]))
- );
- writeApiSearchIndex(pages);
-
- await writeSourceBaseUrl(project);
-}
diff --git a/scripts/apidoc/module-methods.ts b/scripts/apidoc/module-methods.ts
deleted file mode 100644
index c21ccf97..00000000
--- a/scripts/apidoc/module-methods.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import type {
- DeclarationReflection,
- ProjectReflection,
- SignatureReflection,
-} from 'typedoc';
-import type { Method } from '../../docs/.vitepress/components/api-docs/method';
-import { codeToHtml } from './markdown';
-import { analyzeSignature } from './signature';
-import {
- extractDeprecated,
- extractDescription,
- extractJoinedRawExamples,
- extractModuleFieldName,
- extractModuleName,
- selectApiMethodSignatures,
- selectApiModules,
-} from './typedoc';
-import type { ModuleSummary } from './utils';
-import { adjustUrls } from './utils';
-import { writeApiDocsModule } from './writer';
-
-/**
- * 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 async function processModules(
- project: ProjectReflection
-): Promise<ModuleSummary[]> {
- return Promise.all(selectApiModules(project).map(processModule));
-}
-
-/**
- * Analyzes and writes the documentation for a module and its methods such as `faker.animal.cat()`.
- *
- * @param module The module to process.
- *
- * @returns The generated pages.
- */
-async function processModule(
- module: DeclarationReflection
-): Promise<ModuleSummary> {
- const moduleName = extractModuleName(module);
- console.log(`Processing Module ${moduleName}`);
- const moduleFieldName = extractModuleFieldName(module);
- const { comment, deprecated, examples } = analyzeModule(module);
- const methods = await processModuleMethods(
- module,
- `faker.${moduleFieldName}.`
- );
-
- return writeApiDocsModule(
- moduleName,
- moduleFieldName,
- comment,
- examples,
- deprecated,
- methods,
- 'Modules'
- );
-}
-
-/**
- * Analyzes the documentation for a class.
- *
- * @param module The class to process.
- *
- * @returns The class information.
- */
-export function analyzeModule(module: DeclarationReflection): {
- comment: string;
- deprecated: string | undefined;
- examples: string | undefined;
-} {
- const examplesRaw = extractJoinedRawExamples(module);
- const examples = examplesRaw ? codeToHtml(examplesRaw) : undefined;
-
- return {
- comment: adjustUrls(extractDescription(module)),
- deprecated: extractDeprecated(module),
- examples,
- };
-}
-
-/**
- * Processes all api methods of the given class. This does not include the constructor.
- *
- * @param module The module to process.
- * @param accessor The code used to access the methods within the module.
- *
- * @returns A list containing the documentation for the api methods in the given module.
- */
-export async function processModuleMethods(
- module: DeclarationReflection,
- accessor: string
-): Promise<Method[]> {
- return processMethods(selectApiMethodSignatures(module), accessor);
-}
-
-/**
- * Processes all api methods.
- *
- * @param signatures The signatures to process.
- * @param accessor The code used to access the methods.
- *
- * @returns A list containing the documentation for the api methods.
- */
-export async function processMethods(
- signatures: Record<string, SignatureReflection>,
- accessor: string = ''
-): Promise<Method[]> {
- const methods: Method[] = [];
-
- for (const [methodName, signature] of Object.entries(signatures)) {
- console.debug(`- ${methodName}`);
- methods.push(await analyzeSignature(signature, accessor, methodName));
- }
-
- return methods;
-}
diff --git a/scripts/apidoc/parameter-defaults.ts b/scripts/apidoc/parameter-defaults.ts
deleted file mode 100644
index a7f53c2b..00000000
--- a/scripts/apidoc/parameter-defaults.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import type {
- Context,
- DeclarationReflection,
- EventCallback,
- JSONOutput,
- ProjectReflection,
- SerializerComponent,
- SignatureReflection,
-} from 'typedoc';
-import { Reflection, ReflectionKind, TypeScript } from 'typedoc';
-
-const reflectionKindFunctionOrMethod =
- ReflectionKind.Function | ReflectionKind.Method;
-
-interface ParameterDefaultsAware extends Reflection {
- implementationDefaultParameters: Array<string | undefined>;
-}
-
-/**
- * TypeDoc EventCallback for EVENT_CREATE_DECLARATION events that reads the default parameters from the implementation.
- *
- * @param context The converter context.
- * @param reflection The reflection to read the default parameters from.
- */
-export const parameterDefaultReader: EventCallback = (
- context: Context,
- reflection: Reflection
-): void => {
- const symbol = context.project.getSymbolFromReflection(reflection);
- if (!symbol) return;
-
- if (
- reflection.kindOf(reflectionKindFunctionOrMethod) &&
- symbol.declarations?.length
- ) {
- const lastDeclaration = symbol.declarations.at(-1);
- if (TypeScript.isFunctionLike(lastDeclaration)) {
- (reflection as ParameterDefaultsAware).implementationDefaultParameters =
- lastDeclaration.parameters.map((param) =>
- cleanParameterDefault(param.initializer?.getText())
- );
- }
- }
-};
-
-/**
- * Removes compile expressions that don't add any value for readers.
- *
- * @param value The default value to clean.
- *
- * @returns The cleaned default value.
- */
-function cleanParameterDefault(value: string): string;
-function cleanParameterDefault(value?: string): string | undefined;
-function cleanParameterDefault(value?: string): string | undefined {
- if (value == null) {
- return undefined;
- }
-
- // Strip type casts: "'foobar' as unknown as T" => "'foobar'"
- return value.replace(/( as unknown)? as [A-Za-z<>]+/, '');
-}
-
-/**
- * Serializer that adds the `implementationDefaultParameters` to the JSON output.
- */
-export class DefaultParameterAwareSerializer
- implements SerializerComponent<Reflection>
-{
- readonly priority = 0;
-
- supports(item: unknown): item is Reflection {
- return item instanceof Reflection;
- }
-
- toObject(
- item: Reflection,
- obj: Partial<JSONOutput.Reflection>
- ): Partial<JSONOutput.Reflection> {
- (obj as unknown as ParameterDefaultsAware).implementationDefaultParameters =
- (item as ParameterDefaultsAware).implementationDefaultParameters;
- return obj;
- }
-}
-
-/**
- * Replaces all methods' last signature's parameter's default value with the default value read from the implementation.
- *
- * @param project The project to patch.
- */
-export function patchProjectParameterDefaults(
- project: ProjectReflection
-): void {
- const functionOrMethods = project.getReflectionsByKind(
- reflectionKindFunctionOrMethod
- ) as DeclarationReflection[];
- for (const functionOrMethod of functionOrMethods) {
- patchMethodParameterDefaults(functionOrMethod);
- }
-}
-
-/**
- * Replaces the last signature's parameter's default value with the default value read from the implementation.
- *
- * @param method The method to patch.
- */
-function patchMethodParameterDefaults(method: DeclarationReflection): void {
- const signatures = method.signatures;
- const signature = signatures?.[signatures.length - 1];
- const parameterDefaults = (method as unknown as ParameterDefaultsAware)
- .implementationDefaultParameters;
- if (signature && parameterDefaults) {
- patchSignatureParameterDefaults(signature, parameterDefaults);
- }
-}
-
-/**
- * Replaces the given signature's parameter's default value with the given default values.
- *
- * @param signature The signature to patch.
- * @param parameterDefaults The defaults to add.
- */
-function patchSignatureParameterDefaults(
- signature: SignatureReflection,
- parameterDefaults: Array<string | undefined>
-): void {
- const signatureParameters =
- signature.parameters ?? Array.from({ length: parameterDefaults.length });
- if (signatureParameters.length !== parameterDefaults.length) {
- throw new Error('Unexpected parameter length mismatch');
- }
-
- for (const [index, param] of signatureParameters.entries()) {
- param.defaultValue = parameterDefaults[index] || param.defaultValue;
- }
-}
diff --git a/scripts/apidoc/signature.ts b/scripts/apidoc/signature.ts
deleted file mode 100644
index 64910534..00000000
--- a/scripts/apidoc/signature.ts
+++ /dev/null
@@ -1,365 +0,0 @@
-import type {
- Comment,
- DeclarationReflection,
- ParameterReflection,
- Reflection,
- ReflectionType,
- SignatureReflection,
- SomeType,
- Type,
-} from 'typedoc';
-import { ReflectionFlag, ReflectionKind } from 'typedoc';
-import type {
- Method,
- MethodParameter,
-} from '../../docs/.vitepress/components/api-docs/method';
-import { formatTypescript } from './format';
-import { codeToHtml, mdToHtml } from './markdown';
-import {
- extractDeprecated,
- extractDescription,
- extractJoinedRawExamples,
- extractRawDefault,
- extractSeeAlsos,
- extractSince,
- extractSourcePath,
- extractSummaryDefault,
- extractThrows,
- toBlock,
-} from './typedoc';
-
-export async function analyzeSignature(
- signature: SignatureReflection,
- accessor: string,
- methodName: string
-): Promise<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}>`,
- type: parameter.type ? await typeToText(parameter.type) : undefined,
- description: mdToHtml(extractDescription(parameter)),
- });
- }
-
- // Collect Parameters
- const signatureParameters: string[] = [];
- for (
- let index = 0;
- signature.parameters && index < signature.parameters.length;
- index++
- ) {
- const parameter = signature.parameters[index];
-
- const aParam = await analyzeParameter(parameter);
- signatureParameters.push(aParam.signature);
- parameters.push(...aParam.parameters);
- }
-
- // Generate usage section
-
- let signatureTypeParametersString = '';
- if (signatureTypeParameters.length > 0) {
- signatureTypeParametersString = `<${signatureTypeParameters.join(', ')}>`;
- }
-
- const signatureParametersString = signatureParameters.join(', ');
-
- let examples = `${accessor}${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type?.toString()}\n`;
-
- const exampleTags = extractJoinedRawExamples(signature);
- if (exampleTags) {
- examples += exampleTags;
- }
-
- const seeAlsos = extractSeeAlsos(signature).map((seeAlso) =>
- mdToHtml(seeAlso, true)
- );
- const deprecatedMessage = extractDeprecated(signature);
- const deprecated = deprecatedMessage
- ? mdToHtml(deprecatedMessage)
- : undefined;
- const throwsMessage = extractThrows(signature);
- const throws = throwsMessage ? mdToHtml(throwsMessage, true) : undefined;
-
- return {
- name: methodName,
- description: mdToHtml(extractDescription(signature)),
- parameters: parameters,
- since: extractSince(signature),
- sourcePath: extractSourcePath(signature),
- throws,
- returns: await typeToText(signature.type),
- examples: codeToHtml(examples),
- deprecated,
- seeAlsos,
- };
-}
-
-async function analyzeParameter(parameter: ParameterReflection): Promise<{
- parameters: MethodParameter[];
- signature: string;
-}> {
- const name = parameter.name;
- const declarationName = name + (isOptional(parameter) ? '?' : '');
- const type = parameter.type;
- const defaultValue = extractDefaultFromParameter(parameter);
-
- let signatureText = '';
- if (defaultValue) {
- signatureText = ` = ${defaultValue}`;
- }
-
- const signature = `${declarationName}: ${await typeToText(
- type
- )}${signatureText}`;
-
- const parameters: MethodParameter[] = [
- {
- name: declarationName,
- type: await typeToText(type, true),
- default: defaultValue,
- description: mdToHtml(extractDescription(parameter)),
- },
- ];
- parameters.push(...(await analyzeParameterOptions(name, type)));
-
- return {
- parameters,
- signature,
- };
-}
-
-// keep in sync with assertNestedParameterDefault
-async function analyzeParameterOptions(
- name: string,
- parameterType?: SomeType
-): Promise<MethodParameter[]> {
- if (!parameterType) {
- return [];
- }
-
- switch (parameterType.type) {
- case 'array': {
- return analyzeParameterOptions(`${name}[]`, parameterType.elementType);
- }
-
- case 'union': {
- return Promise.all(
- parameterType.types.map((type) => analyzeParameterOptions(name, type))
- ).then((options) => options.flat());
- }
-
- case 'reflection': {
- const properties = parameterType.declaration.children ?? [];
- return Promise.all(
- properties.map(async (property) => {
- const reflection = property.comment
- ? property
- : (property.type as ReflectionType)?.declaration?.signatures?.[0];
- const comment = reflection?.comment;
- const deprecated = extractDeprecated(reflection);
- return {
- name: `${name}.${property.name}${isOptional(property) ? '?' : ''}`,
- type: await declarationTypeToText(property),
- default: extractDefaultFromComment(comment),
- description: mdToHtml(
- toBlock(comment) +
- (deprecated ? `\n\n**DEPRECATED:** ${deprecated}` : '')
- ),
- };
- })
- );
- }
-
- case 'typeOperator': {
- return analyzeParameterOptions(name, parameterType.target);
- }
-
- default: {
- return [];
- }
- }
-}
-
-function isOptional(parameter: Reflection): boolean {
- return parameter.flags.hasFlag(ReflectionFlag.Optional);
-}
-
-async function typeToText(type_?: Type, short = false): Promise<string> {
- if (!type_) {
- return '?';
- }
-
- const type = type_ as SomeType;
- switch (type.type) {
- case 'array': {
- const text = await typeToText(type.elementType, short);
- const isComplexType = text.includes('|') || text.includes('{');
- return isComplexType ? `Array<${text}>` : `${text}[]`;
- }
-
- case 'union': {
- return (await Promise.all(type.types.map((t) => typeToText(t, short))))
- .map((t) => (t.includes('=>') ? `(${t})` : t))
- .sort()
- .join(' | ');
- }
-
- case 'reference': {
- if (!type.typeArguments || type.typeArguments.length === 0) {
- const reflection = type.reflection as DeclarationReflection | undefined;
- const reflectionType = reflection?.type;
- if (
- (reflectionType?.type === 'literal' ||
- reflectionType?.type === 'union') &&
- !type.name.endsWith('Char')
- ) {
- return typeToText(reflectionType, short);
- }
-
- return type.name;
- } else if (type.name === 'LiteralUnion') {
- return [
- await typeToText(type.typeArguments[0], short),
- await typeToText(type.typeArguments[1], short),
- ].join(' | ');
- }
-
- return `${type.name}<${(
- await Promise.all(type.typeArguments.map((t) => typeToText(t, short)))
- ).join(', ')}>`;
- }
-
- case 'reflection': {
- return declarationTypeToText(type.declaration, short);
- }
-
- case 'indexedAccess': {
- return `${await typeToText(type.objectType, short)}[${await typeToText(
- type.indexType,
- short
- )}]`;
- }
-
- case 'literal': {
- return (await formatTypescript(type.toString())).replace(/;\n$/, '');
- }
-
- case 'typeOperator': {
- const text = await typeToText(type.target, short);
- if (short && type.operator === 'readonly') {
- return text;
- }
-
- return `${type.operator} ${text}`;
- }
-
- default: {
- return type.toString();
- }
- }
-}
-
-async function declarationTypeToText(
- declaration: DeclarationReflection,
- short = false
-): Promise<string> {
- switch (declaration.kind) {
- case ReflectionKind.Method: {
- return signatureTypeToText(declaration.signatures?.[0]);
- }
-
- case ReflectionKind.Property: {
- return typeToText(declaration.type);
- }
-
- case ReflectionKind.TypeLiteral: {
- if (declaration.children?.length) {
- if (short) {
- // This is too long for the parameter table, thus we abbreviate this.
- return '{ ... }';
- }
-
- const list = (
- await Promise.all(
- declaration.children.map(
- async (c) => ` ${c.name}: ${await declarationTypeToText(c)}`
- )
- )
- ).join(',\n');
-
- return `{\n${list}\n}`;
- } else if (declaration.signatures?.length) {
- return signatureTypeToText(declaration.signatures[0]);
- }
-
- return declaration.toString();
- }
-
- default: {
- return declaration.toString();
- }
- }
-}
-
-async function signatureTypeToText(
- signature?: SignatureReflection
-): Promise<string> {
- if (!signature) {
- return '(???) => ?';
- }
-
- return `(${(
- await Promise.all(
- signature.parameters?.map(
- async (p) => `${p.name}: ${await typeToText(p.type)}`
- ) ?? []
- )
- ).join(', ')}) => ${await typeToText(signature.type)}`;
-}
-
-/**
- * Extracts and optionally removes the parameter default from the parameter.
- *
- * @param parameter The parameter to extract the default from.
- * @param eraseDefault Whether to erase the default text from the parameter comment.
- *
- * @returns The extracted default value.
- */
-function extractDefaultFromParameter(
- parameter: ParameterReflection,
- eraseDefault = true
-): string | undefined {
- const commentDefault = extractDefaultFromComment(
- parameter.comment,
- eraseDefault
- );
- return parameter.defaultValue ?? commentDefault;
-}
-
-/**
- * Extracts and optionally removes the parameter default from the comments.
- *
- * @param comment The comment to extract the default from.
- * @param eraseDefault Whether to erase the default text from the comment.
- *
- * @returns The extracted default value.
- */
-function extractDefaultFromComment(
- comment?: Comment,
- eraseDefault = true
-): string | undefined {
- if (!comment) {
- return;
- }
-
- const tagDefault = extractRawDefault({ comment });
- const summaryDefault = extractSummaryDefault(comment, eraseDefault);
- return tagDefault || summaryDefault;
-}
diff --git a/scripts/apidoc/typedoc.ts b/scripts/apidoc/typedoc.ts
deleted file mode 100644
index 1cdcf3e1..00000000
--- a/scripts/apidoc/typedoc.ts
+++ /dev/null
@@ -1,421 +0,0 @@
-import type {
- Comment,
- CommentDisplayPart,
- CommentTag,
- DeclarationReflection,
- ProjectReflection,
- Reflection,
- SignatureReflection,
- TypeDocOptions,
-} from 'typedoc';
-import {
- Application,
- Converter,
- ReflectionKind,
- TSConfigReader,
-} from 'typedoc';
-import { faker } from '../../src';
-import {
- DefaultParameterAwareSerializer,
- parameterDefaultReader,
- patchProjectParameterDefaults,
-} from './parameter-defaults';
-import { mapByName } from './utils';
-
-type CommentHolder = Pick<Reflection, 'comment'>;
-
-/**
- * Loads the project using TypeDoc.
- *
- * @param options The options to use for the project.
- *
- * @returns The TypeDoc application and the project reflection.
- */
-export async function loadProject(
- options: Partial<TypeDocOptions> = {
- entryPoints: ['src/index.ts'],
- pretty: true,
- cleanOutputDir: true,
- tsconfig: 'tsconfig.build.json',
- }
-): Promise<[Application, ProjectReflection]> {
- const app = await newTypeDocApp(options);
-
- const project = await app.convert();
-
- if (!project) {
- throw new Error('Failed to convert project');
- }
-
- patchProjectParameterDefaults(project);
-
- return [app, project];
-}
-
-/**
- * Creates and configures a new typedoc application.
- *
- * @param options The options to use for the project.
- */
-async function newTypeDocApp(
- options?: Partial<TypeDocOptions>
-): Promise<Application> {
- const app = await Application.bootstrapWithPlugins(options, [
- new TSConfigReader(),
- ]);
-
- // Read parameter defaults
- app.converter.on(Converter.EVENT_CREATE_DECLARATION, parameterDefaultReader);
- // Add to debug json output
- app.serializer.addSerializer(new DefaultParameterAwareSerializer());
-
- return app;
-}
-
-/**
- * Selects the modules from the project that needs to be documented.
- *
- * @param project The project to extract the modules from.
- * @param includeTestModules Whether to include test modules.
- *
- * @returns The modules to document.
- */
-export function selectApiModules(
- project: ProjectReflection,
- includeTestModules = false
-): DeclarationReflection[] {
- return project
- .getChildrenByKind(ReflectionKind.Class)
- .filter(
- (module) =>
- faker[extractModuleFieldName(module) as keyof typeof faker] != 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)
- .filter((method) => !method.flags.isPrivate);
-}
-
-/**
- * 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.`);
- }
-
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- return signatures.at(-1)!;
-}
-
-/**
- * Selects the method signatures from the module that needs to be documented.
- * Method-Name -> Method-Signature
- *
- * @param module The module to extract the method signatures from.
- *
- * @returns The method signatures to document.
- */
-export function selectApiMethodSignatures(
- module: DeclarationReflection
-): Record<string, SignatureReflection> {
- 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[0].toLowerCase() + moduleName.substring(1);
-}
-
-export const MISSING_DESCRIPTION = 'Missing';
-
-export function toBlock(comment?: Comment): string {
- return joinTagParts(comment?.summary) || MISSING_DESCRIPTION;
-}
-
-export function extractDescription(reflection: Reflection): string {
- return toBlock(reflection.comment);
-}
-
-/**
- * Extracts the source url from the jsdocs.
- *
- * @param reflection The reflection instance to extract the source url from.
- */
-function extractSourceUrl(
- reflection: DeclarationReflection | SignatureReflection
-): string {
- const source = reflection.sources?.[0];
- return source?.url ?? '';
-}
-
-/**
- * Extracts the source base url from the jsdocs.
- *
- * @param reflection The reflection instance to extract the source base url from.
- */
-export function extractSourceBaseUrl(
- reflection: DeclarationReflection | SignatureReflection
-): string {
- return extractSourceUrl(reflection).replace(
- /^(.*\/blob\/[0-9a-f]+\/)(.*)$/,
- '$1'
- );
-}
-
-/**
- * Extracts the relative source path from the jsdocs.
- *
- * @param reflection The reflection instance to extract the source path from.
- */
-export function extractSourcePath(
- reflection: DeclarationReflection | SignatureReflection
-): string {
- return extractSourceUrl(reflection).replace(
- /^(.*\/blob\/[0-9a-f]+\/)(.*)$/,
- '$2'
- );
-}
-
-/**
- * Extracts the text (md) from a jsdoc tag.
- *
- * @param tag The tag to extract the text from.
- * @param reflection The reflection to extract the text from.
- * @param tagProcessor The function used to extract the text from the tag.
- */
-export function extractTagContent(
- tag: `@${string}`,
- reflection?: CommentHolder,
- tagProcessor: (tag: CommentTag) => string[] = joinTagContent
-): string[] {
- const tags =
- reflection?.comment
- ?.getTags(tag)
- .flatMap(tagProcessor)
- .map((tag) => tag.trim()) ?? [];
- if (tags.some((tag) => tag.length === 0)) {
- throw new Error(`Expected non-empty ${tag} tag.`);
- }
-
- return tags;
-}
-
-/**
- * Extracts the text (md) from a single jsdoc tag.
- *
- * @param tag The tag to extract the text from.
- * @param reflection The reflection to extract the text from.
- * @param tagProcessor The function used to extract the text from the tag.
- *
- * @throws If there are multiple tags of that type.
- */
-function extractSingleTagContent(
- tag: `@${string}`,
- reflection?: CommentHolder,
- tagProcessor: (tag: CommentTag) => string[] = joinTagContent
-): string | undefined {
- const tags = extractTagContent(tag, reflection, tagProcessor);
- if (tags.length === 0) {
- return undefined;
- } else if (tags.length === 1) {
- return tags[0];
- }
-
- throw new Error(`Expected 1 ${tag} tag, but got ${tags.length}.`);
-}
-
-/**
- * Extracts the raw code from the jsdocs without the surrounding md code block.
- *
- * @param tag The tag to extract the code from.
- * @param reflection The reflection to extract the code from.
- */
-function extractRawCode(
- tag: `@${string}`,
- reflection?: CommentHolder
-): string[] {
- return extractTagContent(tag, reflection).map((tag) =>
- tag.replace(/^```ts\n/, '').replace(/\n```$/, '')
- );
-}
-
-/**
- * Extracts the default from the jsdocs without the surrounding md code block.
- *
- * @param reflection The reflection to extract the examples from.
- */
-export function extractRawDefault(reflection?: CommentHolder): string {
- return extractRawCode('@default', reflection)[0] ?? '';
-}
-
-/**
- * Extracts and optionally removes the default from the comment summary.
- *
- * @param comment The comment to extract the default from.
- * @param eraseDefault Whether to erase the default text from the comment.
- *
- * @returns The extracted default value.
- */
-export function extractSummaryDefault(
- comment?: Comment,
- eraseDefault = true
-): string | undefined {
- if (!comment) {
- return;
- }
-
- const summary = comment.summary;
- const text = joinTagParts(summary).trim();
- if (!text) {
- return;
- }
-
- const result = /^(.*)[ \n]Defaults to `([^`]+)`\.(.*)$/s.exec(text);
- if (!result) {
- return;
- }
-
- if (result[3].trim()) {
- throw new Error(`Found description text after the default value:\n${text}`);
- }
-
- if (eraseDefault) {
- summary.splice(-2, 2);
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const lastSummaryPart = summary.at(-1)!;
- lastSummaryPart.text = lastSummaryPart.text.replace(
- /[ \n]Defaults to $/,
- ''
- );
- }
-
- return result[2];
-}
-
-/**
- * Extracts the examples from the jsdocs without the surrounding md code block.
- *
- * @param reflection The reflection to extract the examples from.
- */
-function extractRawExamples(reflection?: CommentHolder): string[] {
- return extractRawCode('@example', reflection);
-}
-
-/**
- * Extracts the examples from the jsdocs without the surrounding md code block, then joins them with newlines and trims.
- *
- * @param reflection The reflection to extract the examples from.
- */
-export function extractJoinedRawExamples(
- reflection?: CommentHolder
-): string | undefined {
- const examples = extractRawExamples(reflection);
- return examples.length === 0 ? undefined : examples.join('\n').trim();
-}
-
-/**
- * Extracts all the `@see` references from the jsdocs separately.
- *
- * @param reflection The reflection to extract the see also references from.
- */
-export function extractSeeAlsos(reflection?: CommentHolder): string[] {
- return extractTagContent('@see', reflection, (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).trimStart();
- }
-
- return link;
- })
- .filter((link) => link.length > 0)
- );
-}
-
-/**
- * Joins the parts of the given jsdocs tag.
- *
- * @param tag The tag to join the parts of.
- */
-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 reflection is deprecated.
- *
- * @param reflection The reflection to check.
- *
- * @returns The message explaining the deprecation if deprecated, otherwise `undefined`.
- */
-export function extractDeprecated(
- reflection?: CommentHolder
-): string | undefined {
- return extractSingleTagContent('@deprecated', reflection);
-}
-
-/**
- * Extracts the "throws" tag from the provided signature.
- *
- * @param reflection The reflection to check.
- *
- * @returns The message explaining the conditions when this method throws. Or `undefined` if it does not throw.
- */
-export function extractThrows(reflection?: CommentHolder): string | undefined {
- const content = extractTagContent('@throws', reflection).join('\n');
- return content.length === 0 ? undefined : content;
-}
-
-/**
- * Extracts the "since" tag from the provided signature.
- *
- * @param reflection The signature to check.
- *
- * @returns The contents of the `@since` tag.
- */
-export function extractSince(reflection: CommentHolder): string {
- return extractSingleTagContent('@since', reflection) || MISSING_DESCRIPTION;
-}
diff --git a/scripts/apidoc/utils.ts b/scripts/apidoc/utils.ts
deleted file mode 100644
index b23f3568..00000000
--- a/scripts/apidoc/utils.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { createHash } from 'node:crypto';
-import { dirname, resolve } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import type { Method } from '../../docs/.vitepress/components/api-docs/method';
-
-// Types
-
-export type Page = { text: string; link: string; category: string };
-
-export type ModuleSummary = Page & {
- methods: Method[];
- diff: DocsApiDiff;
-};
-
-export interface DocsApiDiffIndex {
- /**
- * The methods in the module by name.
- */
- [module: string]: DocsApiDiff;
-}
-
-export interface DocsApiDiff {
- /**
- * The checksum of the entire module.
- */
- moduleHash: string;
- /**
- * The checksum of the method by name.
- */
- [method: string]: string;
-}
-
-// Paths
-
-const pathRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
-export const pathDocsDir = resolve(pathRoot, 'docs');
-const pathPublicDir = resolve(pathDocsDir, 'public');
-export const nameDocsDiffIndexFile = 'api-diff-index.json';
-export const pathDocsDiffIndexFile = resolve(
- pathPublicDir,
- nameDocsDiffIndexFile
-);
-export const pathOutputDir = resolve(pathDocsDir, 'api');
-
-// Functions
-
-export function adjustUrls(description: string): string {
- return description.replaceAll(/https:\/\/(next.)?fakerjs.dev\//g, '/');
-}
-
-export function mapByName<TInput extends { name: string }, TValue>(
- input: TInput[],
- valueExtractor: (item: TInput) => TValue
-): Record<string, TValue> {
- return Object.fromEntries(
- input.map((item) => [item.name, valueExtractor(item)])
- );
-}
-
-/**
- * Creates a diff hash for the given method by removing the line number from the source path.
- *
- * @param method The method to create a hash for.
- */
-export function methodDiffHash(method: Method): string {
- return diffHash({
- ...method,
- sourcePath: method.sourcePath.replaceAll(/#.*/g, ''),
- });
-}
-
-/**
- * Creates a diff hash for the given object.
- *
- * @param object The object to create a hash for.
- */
-export function diffHash(object: unknown): string {
- return createHash('md5').update(JSON.stringify(object)).digest('hex');
-}
diff --git a/scripts/apidoc/writer.ts b/scripts/apidoc/writer.ts
deleted file mode 100644
index b03cfe85..00000000
--- a/scripts/apidoc/writer.ts
+++ /dev/null
@@ -1,249 +0,0 @@
-import { writeFileSync } from 'node:fs';
-import { resolve } from 'node:path';
-import type { ProjectReflection } from 'typedoc';
-import { ReflectionKind } from 'typedoc';
-import type { DefaultTheme } from 'vitepress';
-import type { Method } from '../../docs/.vitepress/components/api-docs/method';
-import type { APIGroup } from '../../docs/api/api-types';
-import { groupBy } from '../../src/internal/group-by';
-import { formatMarkdown, formatTypescript } from './format';
-import { extractSourceBaseUrl } from './typedoc';
-import type { DocsApiDiffIndex, ModuleSummary, Page } from './utils';
-import {
- diffHash,
- methodDiffHash,
- pathDocsDiffIndexFile,
- pathDocsDir,
- pathOutputDir,
-} from './utils';
-
-const pathDocsApiPages = resolve(pathDocsDir, '.vitepress', 'api-pages.ts');
-const pathDocsApiSearchIndex = resolve(
- pathDocsDir,
- 'api',
- 'api-search-index.json'
-);
-
-const scriptCommand = 'pnpm run generate:api-docs';
-
-// Moved here because this must not be formatted by prettier
-const vitePressInFileOptions = `---
-editLink: false
----
-
-`;
-
-/**
- * Writes the api docs for the given modules.
- *
- * @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.
- * @param examples The example code.
- * @param deprecated The deprecation message.
- * @param methods The methods of the module.
- * @param category The category of the module.
- */
-export async function writeApiDocsModule(
- moduleName: string,
- lowerModuleName: string,
- comment: string,
- examples: string | undefined,
- deprecated: string | undefined,
- methods: Method[],
- category: string
-): Promise<ModuleSummary> {
- await writeApiDocsModulePage(
- moduleName,
- lowerModuleName,
- comment,
- examples,
- deprecated,
- methods
- );
- writeApiDocsModuleData(lowerModuleName, methods);
-
- return {
- text: moduleName,
- link: `/api/${lowerModuleName}.html`,
- methods,
- category,
- diff: {
- moduleHash: diffHash({
- name: moduleName,
- field: lowerModuleName,
- deprecated,
- comment,
- }),
- ...Object.fromEntries(
- methods.map((method) => [method.name, methodDiffHash(method)])
- ),
- },
- };
-}
-
-/**
- * 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.
- * @param examples The example code.
- * @param deprecated The deprecation message.
- * @param methods The methods of the module.
- */
-async function writeApiDocsModulePage(
- moduleName: string,
- lowerModuleName: string,
- comment: string,
- examples: string | undefined,
- deprecated: string | undefined,
- methods: Method[]
-): Promise<void> {
- // Write api docs page
- let content = `
- <script setup>
- import ApiDocsMethod from '../.vitepress/components/api-docs/method.vue';
- import ${lowerModuleName} from './${lowerModuleName}.json';
- </script>
-
- <!-- This file is automatically generated. -->
- <!-- Run '${scriptCommand}' to update -->
-
- # ${moduleName}
-
- ::: v-pre
-
- ${
- deprecated == null
- ? ''
- : `<div class="warning custom-block">
- <p class="custom-block-title">Deprecated</p>
- <p>This module is deprecated and will be removed in a future version.</p>
- <span>${deprecated}</span>
- </div>`
- }
-
- ${comment}
-
- ${examples == null ? '' : `<div class="examples">${examples}</div>`}
-
- :::
-
- ${methods
- .map(
- (method) => `
- ## ${method.name}
-
- <ApiDocsMethod :method="${lowerModuleName}.${method.name}" v-once />
- `
- )
- .join('')}
- `.replaceAll(/\n +/g, '\n');
-
- content = vitePressInFileOptions + (await formatMarkdown(content));
-
- writeFileSync(resolve(pathOutputDir, `${lowerModuleName}.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.
- */
-function writeApiDocsModuleData(
- lowerModuleName: string,
- methods: Method[]
-): void {
- const content = JSON.stringify(
- Object.fromEntries(methods.map((method) => [method.name, method]))
- );
-
- writeFileSync(resolve(pathOutputDir, `${lowerModuleName}.json`), content);
-}
-
-/**
- * Writes the api docs index to correct location.
- *
- * @param pages The pages to write into the index.
- */
-export async function writeApiPagesIndex(pages: Page[]): Promise<void> {
- const pagesByCategory: Record<string, DefaultTheme.SidebarItem[]> = groupBy(
- pages,
- (page) => page.category,
- ({ text, link }) => ({ text, link })
- );
- const pageTree = Object.entries(pagesByCategory).flatMap(
- ([category, items]) => (category ? [{ text: category, items }] : items)
- );
-
- // Write api-pages.ts
- console.log('Updating api-pages.ts');
- pageTree.splice(0, 0, { text: 'Overview', link: '/api/' });
- let apiPagesContent = `
- // This file is automatically generated.
- // Run '${scriptCommand}' to update
- export const apiPages = ${JSON.stringify(pageTree)};
- `.replace(/\n +/, '\n');
-
- apiPagesContent = await formatTypescript(apiPagesContent);
-
- writeFileSync(pathDocsApiPages, apiPagesContent);
-}
-
-/**
- * Writes the api diff index to the correct location.
- *
- * @param diffIndex The diff index project to write.
- */
-export function writeApiDiffIndex(diffIndex: DocsApiDiffIndex): void {
- writeFileSync(pathDocsDiffIndexFile, JSON.stringify(diffIndex));
-}
-
-/**
- * Writes the api search index to the correct location.
- *
- * @param pages The pages to write into the index.
- */
-export function writeApiSearchIndex(pages: ModuleSummary[]): void {
- const apiIndex: APIGroup[] = [
- {
- text: 'Module API',
- items: pages.map((module) => ({
- text: module.text,
- link: module.link,
- headers: module.methods.map((method) => ({
- anchor: method.name,
- text: method.name,
- deprecated: !!method.deprecated,
- })),
- })),
- },
- ];
-
- writeFileSync(pathDocsApiSearchIndex, JSON.stringify(apiIndex));
-}
-
-/**
- * Writes the source base url to the correct location.
- *
- * @param project The typedoc project.
- */
-export async function writeSourceBaseUrl(
- project: ProjectReflection
-): Promise<void> {
- const baseUrl = extractSourceBaseUrl(
- project.getChildrenByKind(ReflectionKind.Class)[0]
- );
-
- let content = `
- // This file is automatically generated.
- // Run '${scriptCommand}' to update
- export const sourceBaseUrl = '${baseUrl}';
- `.replace(/\n +/, '\n');
-
- content = await formatTypescript(content);
-
- writeFileSync(resolve(pathOutputDir, 'source-base-url.ts'), content);
-}
diff --git a/scripts/apidocs.ts b/scripts/apidocs.ts
new file mode 100644
index 00000000..4a109006
--- /dev/null
+++ b/scripts/apidocs.ts
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+
+import { generate } from './apidocs/generate';
+import { initMarkdownRenderer } from './apidocs/utils/markdown';
+
+await initMarkdownRenderer();
+await generate();
diff --git a/scripts/apidoc/diff.ts b/scripts/apidocs/diff.ts
index 101200cd..0ccdad6e 100644
--- a/scripts/apidoc/diff.ts
+++ b/scripts/apidocs/diff.ts
@@ -1,12 +1,15 @@
-import type { DocsApiDiffIndex } from './utils';
-import { nameDocsDiffIndexFile, pathDocsDiffIndexFile } from './utils';
+import type { ApiDiffHashes } from './output/diff-index';
+import {
+ FILE_NAME_DOCS_DIFF_INDEX,
+ FILE_PATH_DOCS_DIFF_INDEX,
+} from './output/diff-index';
/**
* Loads the diff index from the given source url.
*
* @param url The url to load the diff index from.
*/
-async function loadRemote(url: string): Promise<DocsApiDiffIndex> {
+async function loadRemote(url: string): Promise<ApiDiffHashes> {
return fetch(url).then((res) => {
if (!res.ok) {
throw new Error(
@@ -14,7 +17,7 @@ async function loadRemote(url: string): Promise<DocsApiDiffIndex> {
);
}
- return res.json() as Promise<DocsApiDiffIndex>;
+ return res.json() as Promise<ApiDiffHashes>;
});
}
@@ -23,8 +26,8 @@ async function loadRemote(url: string): Promise<DocsApiDiffIndex> {
*
* @param path The path to load the diff index from. Should start with `file://` for cross platform compatibility.
*/
-async function loadLocal(path: string): Promise<DocsApiDiffIndex> {
- return import(path).then((imp) => imp.default as DocsApiDiffIndex);
+async function loadLocal(path: string): Promise<ApiDiffHashes> {
+ return import(path).then((imp) => imp.default as ApiDiffHashes);
}
/**
@@ -34,7 +37,7 @@ async function loadLocal(path: string): Promise<DocsApiDiffIndex> {
*
* @param source The source to load the diff index from.
*/
-async function load(source: string): Promise<DocsApiDiffIndex> {
+async function load(source: string): Promise<ApiDiffHashes> {
return source.startsWith('https://') ? loadRemote(source) : loadLocal(source);
}
@@ -58,8 +61,8 @@ function allKeys(
* @param sourceDiffIndex The path to the source (changed) index. Defaults to the local diff index.
*/
export async function diff(
- targetDiffIndex = `https://next.fakerjs.dev/${nameDocsDiffIndexFile}`,
- sourceDiffIndex = `file://${pathDocsDiffIndexFile}`
+ targetDiffIndex = `https://next.fakerjs.dev/${FILE_NAME_DOCS_DIFF_INDEX}`,
+ sourceDiffIndex = `file://${FILE_PATH_DOCS_DIFF_INDEX}`
): Promise<Record<string, ['ADDED'] | ['REMOVED'] | string[]>> {
const target = await load(targetDiffIndex);
const source = await load(sourceDiffIndex);
diff --git a/scripts/apidocs/generate.ts b/scripts/apidocs/generate.ts
new file mode 100644
index 00000000..81021c5f
--- /dev/null
+++ b/scripts/apidocs/generate.ts
@@ -0,0 +1,45 @@
+import type { Project } from 'ts-morph';
+import { writeDiffIndex } from './output/diff-index';
+import { writePages } from './output/page';
+import { writePageIndex } from './output/page-index';
+import { writeSearchIndex } from './output/search-index';
+import { writeSourceBaseUrl } from './output/source-base-url';
+import type { RawApiDocsPage } from './processing/class';
+import {
+ processModuleClasses,
+ processProjectClasses,
+ processProjectInterfaces,
+ processProjectUtilities,
+} from './processing/class';
+import { getProject } from './project';
+
+export async function generate(): Promise<void> {
+ console.log('Reading project');
+ const project = getProject();
+ console.log('Processing components');
+ const apiDocsPages = processComponents(project);
+ console.log('Writing files');
+ await writeFiles(apiDocsPages);
+}
+
+export function processComponents(project: Project): RawApiDocsPage[] {
+ return [
+ ...processProjectClasses(project),
+ ...processProjectInterfaces(project),
+ processProjectUtilities(project),
+ ...processModuleClasses(project),
+ ];
+}
+
+async function writeFiles(apiDocsPages: RawApiDocsPage[]): Promise<void> {
+ console.log('- diff index');
+ writeDiffIndex(apiDocsPages);
+ console.log('- page index');
+ await writePageIndex(apiDocsPages);
+ console.log('- pages');
+ await writePages(apiDocsPages);
+ console.log('- search index');
+ writeSearchIndex(apiDocsPages);
+ console.log('- source base url');
+ await writeSourceBaseUrl();
+}
diff --git a/scripts/apidocs/output/constants.ts b/scripts/apidocs/output/constants.ts
new file mode 100644
index 00000000..ec579e3c
--- /dev/null
+++ b/scripts/apidocs/output/constants.ts
@@ -0,0 +1 @@
+export const SCRIPT_COMMAND = 'pnpm run generate:api-docs';
diff --git a/scripts/apidocs/output/diff-index.ts b/scripts/apidocs/output/diff-index.ts
new file mode 100644
index 00000000..f32c5dfe
--- /dev/null
+++ b/scripts/apidocs/output/diff-index.ts
@@ -0,0 +1,81 @@
+import { createHash } from 'node:crypto';
+import { writeFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import type { RawApiDocsPage } from '../processing/class';
+import type { RawApiDocsMethod } from '../processing/method';
+import { FILE_PATH_PUBLIC } from '../utils/paths';
+
+export const FILE_NAME_DOCS_DIFF_INDEX = 'api-diff-index.json';
+export const FILE_PATH_DOCS_DIFF_INDEX = resolve(
+ FILE_PATH_PUBLIC,
+ FILE_NAME_DOCS_DIFF_INDEX
+);
+
+/**
+ * The diff hashes for the entire api.
+ */
+export interface ApiDiffHashes {
+ /**
+ * The pages with their diff hashes.
+ */
+ [pages: string]: ApiPageDiffHashes;
+}
+
+/**
+ * The diff hashes for a single api doc page.
+ */
+export interface ApiPageDiffHashes {
+ /**
+ * The checksum of the entire page.
+ */
+ pageHash: string;
+ /**
+ * The checksum of the method by name.
+ */
+ [method: string]: string;
+}
+
+/**
+ * Writes the api diff index to the correct location.
+ *
+ * @param pages The pages to write into the index.
+ */
+export function writeDiffIndex(pages: RawApiDocsPage[]): void {
+ const diffIndex: ApiDiffHashes = Object.fromEntries(
+ pages.map((page) => [page.title, pageDiffHashes(page)])
+ );
+ writeFileSync(FILE_PATH_DOCS_DIFF_INDEX, JSON.stringify(diffIndex));
+}
+
+function pageDiffHashes(page: RawApiDocsPage): ApiPageDiffHashes {
+ return {
+ pageHash: diffHash({
+ ...page,
+ methods: undefined,
+ } satisfies Partial<RawApiDocsPage>),
+ ...Object.fromEntries(
+ page.methods.map((method) => [method.name, methodDiffHash(method)])
+ ),
+ };
+}
+
+/**
+ * Creates a diff hash for the given method by removing the line number from the source path.
+ *
+ * @param method The method to create a hash for.
+ */
+function methodDiffHash(method: RawApiDocsMethod): string {
+ return diffHash({
+ ...method,
+ source: method.source.filePath,
+ } satisfies Record<keyof RawApiDocsMethod, unknown>);
+}
+
+/**
+ * Creates a diff hash for the given object.
+ *
+ * @param object The object to create a hash for.
+ */
+function diffHash(object: unknown): string {
+ return createHash('md5').update(JSON.stringify(object)).digest('hex');
+}
diff --git a/scripts/apidocs/output/page-index.ts b/scripts/apidocs/output/page-index.ts
new file mode 100644
index 00000000..b676ca24
--- /dev/null
+++ b/scripts/apidocs/output/page-index.ts
@@ -0,0 +1,38 @@
+import { writeFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import type { DefaultTheme } from 'vitepress';
+import { groupBy } from '../../../src/internal/group-by';
+import type { RawApiDocsPage } from '../processing/class';
+import { formatTypescript } from '../utils/format';
+import { FILE_PATH_DOCS } from '../utils/paths';
+import { SCRIPT_COMMAND } from './constants';
+
+const pathDocsApiPages = resolve(FILE_PATH_DOCS, '.vitepress', 'api-pages.ts');
+
+/**
+ * Writes the api docs index to correct location.
+ *
+ * @param pages The pages to write into the index.
+ */
+export async function writePageIndex(pages: RawApiDocsPage[]): Promise<void> {
+ const pagesByCategory: Record<string, DefaultTheme.SidebarItem[]> = groupBy(
+ pages,
+ (page) => page.category ?? '',
+ ({ title: text, camelTitle }) => ({ text, link: `/api/${camelTitle}.html` })
+ );
+ const pageTree = Object.entries(pagesByCategory).flatMap(
+ ([category, items]) => (category ? [{ text: category, items }] : items)
+ );
+
+ // Write api-pages.ts
+ pageTree.unshift({ text: 'Overview', link: '/api/' });
+ let apiPagesContent = `
+ // This file is automatically generated.
+ // Run '${SCRIPT_COMMAND}' to update
+ export const apiPages = ${JSON.stringify(pageTree)};
+ `.replace(/\n +/, '\n');
+
+ apiPagesContent = await formatTypescript(apiPagesContent);
+
+ writeFileSync(pathDocsApiPages, apiPagesContent);
+}
diff --git a/scripts/apidocs/output/page.ts b/scripts/apidocs/output/page.ts
new file mode 100644
index 00000000..ae1c3c4f
--- /dev/null
+++ b/scripts/apidocs/output/page.ts
@@ -0,0 +1,172 @@
+import { writeFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import type { ApiDocsMethod } from '../../../docs/.vitepress/components/api-docs/method';
+import type { RawApiDocsPage } from '../processing/class';
+import type { RawApiDocsMethod } from '../processing/method';
+import { formatMarkdown } from '../utils/format';
+import { adjustUrls, codeToHtml, mdToHtml } from '../utils/markdown';
+import { FILE_PATH_API_DOCS } from '../utils/paths';
+import { required } from '../utils/value-checks';
+import { SCRIPT_COMMAND } from './constants';
+
+// Extracted to a constant because the contents must not be formatted by prettier
+const vitePressInFileOptions = `---
+editLink: false
+---
+
+`;
+
+/**
+ * Writes the api docs page and data for the given modules to the correct location.
+ *
+ * @param pages The pages to write.
+ */
+export async function writePages(pages: RawApiDocsPage[]): Promise<void> {
+ await Promise.all(pages.map(writePage));
+}
+
+/**
+ * Writes the api docs page and data for the given module to the correct location.
+ *
+ * @param page The page to write.
+ */
+async function writePage(page: RawApiDocsPage): Promise<void> {
+ try {
+ await writePageMarkdown(page);
+ writePageJsonData(page);
+ } catch (error) {
+ throw new Error(`Error writing page ${page.title}`, { cause: error });
+ }
+}
+
+/**
+ * Writes the api docs page for the given module to the correct location.
+ *
+ * @param page The page to write.
+ */
+async function writePageMarkdown(page: RawApiDocsPage): Promise<void> {
+ const { title, camelTitle, deprecated, description, examples, methods } =
+ page;
+ // Write api docs page
+ let content = `
+ <script setup>
+ import ApiDocsMethod from '../.vitepress/components/api-docs/method.vue';
+ import ${camelTitle} from './${camelTitle}.json';
+ </script>
+
+ <!-- This file is automatically generated. -->
+ <!-- Run '${SCRIPT_COMMAND}' to update -->
+
+ # ${title}
+
+ ::: v-pre
+
+ ${
+ deprecated == null
+ ? ''
+ : `<div class="warning custom-block">
+ <p class="custom-block-title">Deprecated</p>
+ <p>This module is deprecated and will be removed in a future version.</p>
+ <span>${deprecated}</span>
+ </div>`
+ }
+
+ ${adjustUrls(description)}
+
+ ${examples.length === 0 ? '' : `<div class="examples">${codeToHtml(examples.join('\n'))}</div>`}
+
+ :::
+
+ ${methods
+ .map(
+ (method) => `
+ ## ${method.name}
+
+ <ApiDocsMethod :method="${camelTitle}.${method.name}" v-once />
+ `
+ )
+ .join('')}
+ `.replaceAll(/\n +/g, '\n');
+
+ content = vitePressInFileOptions + (await formatMarkdown(content));
+
+ writeFileSync(resolve(FILE_PATH_API_DOCS, `${camelTitle}.md`), content);
+}
+
+/**
+ * Writes the api docs data for the given module to correct location.
+ *
+ * @param page The page to write.
+ */
+function writePageJsonData(page: RawApiDocsPage): void {
+ const { camelTitle, methods } = page;
+ const pageData: Record<string, ApiDocsMethod> = Object.fromEntries(
+ methods.map((method) => [method.name, toMethodData(method)])
+ );
+ const content = JSON.stringify(pageData, null, 2);
+
+ writeFileSync(resolve(FILE_PATH_API_DOCS, `${camelTitle}.json`), content);
+}
+
+const defaultCommentRegex = /\s+Defaults to `([^`]+)`\..*/;
+
+function toMethodData(method: RawApiDocsMethod): ApiDocsMethod {
+ const { name, signatures, source } = method;
+ const signatureData = required(signatures.at(-1), 'method signature');
+ const {
+ deprecated,
+ description,
+ since,
+ parameters,
+ returns,
+ throws,
+ signature,
+ examples,
+ seeAlsos,
+ } = signatureData;
+ const { filePath, line } = source;
+
+ /* Target order, omitted to improve diff to old files
+ return {
+ name,
+ deprecated: mdToHtml(deprecated),
+ description: mdToHtml(description),
+ since,
+ parameters: parameters.map((param) => ({
+ ...param,
+ type: param.type.text,
+ default:
+ param.default ?? defaultCommentRegex.exec(param.description)?.[1],
+ description: mdToHtml(param.description.replace(defaultCommentRegex, '')),
+ })),
+ returns: returns.text,
+ throws: throws.length === 0 ? undefined : mdToHtml(throws.join('\n'), true),
+ // signature: codeToHtml(signature),
+ examples: codeToHtml([signature, ...examples].join('\n')),
+ seeAlsos: seeAlsos.map((seeAlso) => mdToHtml(seeAlso, true)),
+ sourcePath: sourcePath.replace(/:(\d+):\d+/g, '#L$1'),
+ };
+ */
+
+ return {
+ name,
+ description: mdToHtml(description),
+ parameters: parameters.map((param) => ({
+ ...param,
+ type: param.type.text,
+ default: param.default ?? extractSummaryDefault(param.description),
+ description: mdToHtml(param.description.replace(defaultCommentRegex, '')),
+ })),
+ since,
+ sourcePath: `${filePath}#L${line}`,
+ throws: throws.length === 0 ? undefined : mdToHtml(throws.join('\n'), true),
+ returns: returns.text,
+ examples: codeToHtml([signature, ...examples].join('\n')),
+ deprecated: mdToHtml(deprecated),
+ seeAlsos: seeAlsos.map((seeAlso) => mdToHtml(seeAlso, true)),
+ };
+}
+
+export function extractSummaryDefault(description: string): string | undefined {
+ return defaultCommentRegex.exec(description)?.[1];
+}
diff --git a/scripts/apidocs/output/search-index.ts b/scripts/apidocs/output/search-index.ts
new file mode 100644
index 00000000..9bff4a4e
--- /dev/null
+++ b/scripts/apidocs/output/search-index.ts
@@ -0,0 +1,34 @@
+import { writeFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import type { APIGroup } from '../../../docs/api/api-types';
+import type { RawApiDocsPage } from '../processing/class';
+import { FILE_PATH_API_DOCS } from '../utils/paths';
+
+const pathDocsApiSearchIndex = resolve(
+ FILE_PATH_API_DOCS,
+ 'api-search-index.json'
+);
+
+/**
+ * Writes the api search index to the correct location.
+ *
+ * @param pages The pages to write into the index.
+ */
+export function writeSearchIndex(pages: RawApiDocsPage[]): void {
+ const apiIndex: APIGroup[] = [
+ {
+ text: 'Module API',
+ items: pages.map((page) => ({
+ text: page.title,
+ link: `/api/${page.camelTitle}.html`,
+ headers: page.methods.map((method) => ({
+ anchor: method.name,
+ text: method.name,
+ deprecated: method.signatures.every((s) => !!s.deprecated),
+ })),
+ })),
+ },
+ ];
+
+ writeFileSync(pathDocsApiSearchIndex, JSON.stringify(apiIndex));
+}
diff --git a/scripts/apidocs/output/source-base-url.ts b/scripts/apidocs/output/source-base-url.ts
new file mode 100644
index 00000000..ca6df85a
--- /dev/null
+++ b/scripts/apidocs/output/source-base-url.ts
@@ -0,0 +1,38 @@
+import { execSync } from 'node:child_process';
+import { writeFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { formatTypescript } from '../utils/format';
+import { FILE_PATH_API_DOCS } from '../utils/paths';
+import { SCRIPT_COMMAND } from './constants';
+
+const pathSourceBaseUrlFile = resolve(FILE_PATH_API_DOCS, 'source-base-url.ts');
+
+/**
+ * Writes the source base url to the correct location.
+ */
+export async function writeSourceBaseUrl(): Promise<void> {
+ const baseUrl = getSourceBaseUrl();
+
+ let content = `
+ // This file is automatically generated.
+ // Run '${SCRIPT_COMMAND}' to update
+ export const sourceBaseUrl = '${baseUrl}';
+ `.replace(/\n +/, '\n');
+
+ content = await formatTypescript(content);
+
+ writeFileSync(pathSourceBaseUrlFile, content);
+}
+
+function getSourceBaseUrl(): string {
+ return `https://github.com/faker-js/faker/blob/${getCommitHash() || 'next'}/`;
+}
+
+function getCommitHash(): string | undefined {
+ try {
+ return execSync('git rev-parse --verify HEAD').toString('utf8').trim();
+ } catch (error) {
+ console.warn('Failed to get commit hash', error);
+ return undefined;
+ }
+}
diff --git a/scripts/apidocs/processing/class.ts b/scripts/apidocs/processing/class.ts
new file mode 100644
index 00000000..5af5e874
--- /dev/null
+++ b/scripts/apidocs/processing/class.ts
@@ -0,0 +1,223 @@
+import type { ClassDeclaration, InterfaceDeclaration, Project } from 'ts-morph';
+import { required, valuesForKeys } from '../utils/value-checks';
+import { newProcessingError } from './error';
+import type { JSDocableLikeNode } from './jsdocs';
+import {
+ getDeprecated,
+ getDescription,
+ getExamples,
+ getJsDocs,
+} from './jsdocs';
+import type { RawApiDocsMethod } from './method';
+import {
+ processClassConstructors,
+ processClassMethods,
+ processInterfaceMethods,
+ processProjectFunctions,
+} from './method';
+
+/**
+ * Represents a raw page in the API docs.
+ */
+export interface RawApiDocsPage {
+ /**
+ * The title of the page as shown to users.
+ */
+ title: string;
+ /**
+ * The title of the page in camel case as used in paths.
+ */
+ camelTitle: string;
+ /**
+ * The category of the page, if it has one.
+ */
+ category: string | undefined;
+ /**
+ * The deprecation notice of the page, if it has one.
+ */
+ deprecated: string | undefined;
+ /**
+ * The description of the page.
+ */
+ description: string;
+ /**
+ * The usage examples of the elements on the page.
+ */
+ examples: string[];
+ /**
+ * The api methods on the page.
+ */
+ methods: RawApiDocsMethod[];
+}
+
+// Classes
+
+function getAllClasses(
+ project: Project,
+ filter: (name: string) => boolean = () => true
+): Record<string, ClassDeclaration> {
+ return Object.fromEntries(
+ project
+ .getSourceFiles()
+ .flatMap((file) => file.getClasses())
+ .map((clazz) => [clazz.getNameOrThrow(), clazz] as const)
+ .filter(([name]) => filter(name))
+ );
+}
+
+export function processProjectClasses(project: Project): RawApiDocsPage[] {
+ return processClasses(
+ valuesForKeys(getAllClasses(project), ['Faker', 'SimpleFaker'])
+ );
+}
+
+function processClasses(classes: ClassDeclaration[]): RawApiDocsPage[] {
+ return classes.map((clazz) => {
+ try {
+ return processClass(clazz);
+ } catch (error) {
+ throw newProcessingError({
+ type: 'class',
+ name: clazz.getNameOrThrow(),
+ source: clazz,
+ cause: error,
+ });
+ }
+ });
+}
+
+export function processClass(clazz: ClassDeclaration): RawApiDocsPage {
+ const result = processModule(clazz);
+ result.methods.unshift(...processClassConstructors(clazz));
+ return result;
+}
+
+// Modules
+
+export function processModuleClasses(project: Project): RawApiDocsPage[] {
+ return processModules(
+ Object.values(
+ getAllClasses(
+ project,
+ (module: string): boolean =>
+ module.endsWith('Module') && !module.startsWith('Simple')
+ )
+ ).sort((a, b) => a.getNameOrThrow().localeCompare(b.getNameOrThrow()))
+ );
+}
+
+function processModules(modules: ClassDeclaration[]): RawApiDocsPage[] {
+ return modules.map((module) => {
+ try {
+ return processModule(module, 'Modules');
+ } catch (error: unknown) {
+ throw newProcessingError({
+ type: 'module',
+ name: getModuleName(module),
+ source: module,
+ cause: error,
+ });
+ }
+ });
+}
+
+function processModule(
+ module: ClassDeclaration,
+ category: string | undefined = undefined
+): RawApiDocsPage {
+ const title = getModuleName(module);
+
+ return {
+ ...preparePage(module, title, category),
+ methods: processClassMethods(module),
+ };
+}
+
+function getModuleName(module: ClassDeclaration): string {
+ return required(module.getName(), 'module name').replace(/Module$/, '');
+}
+
+// Interfaces
+
+function getAllInterfaces(
+ project: Project
+): Record<string, InterfaceDeclaration> {
+ return Object.fromEntries(
+ project
+ .getSourceFiles()
+ .flatMap((file) => file.getInterfaces())
+ .map((iface) => [iface.getName(), iface] as const)
+ );
+}
+
+export function processProjectInterfaces(project: Project): RawApiDocsPage[] {
+ return processInterfaces(
+ valuesForKeys(getAllInterfaces(project), ['Randomizer'])
+ );
+}
+
+function processInterfaces(
+ interfaces: InterfaceDeclaration[]
+): RawApiDocsPage[] {
+ return interfaces.map((iface) => {
+ try {
+ return processInterface(iface);
+ } catch (error) {
+ throw newProcessingError({
+ type: 'interface',
+ name: iface.getName(),
+ source: iface,
+ cause: error,
+ });
+ }
+ });
+}
+
+function processInterface(iface: InterfaceDeclaration): RawApiDocsPage {
+ return {
+ ...preparePage(iface, iface.getName()),
+ methods: processInterfaceMethods(iface),
+ };
+}
+
+// Utilities
+
+export function processProjectUtilities(project: Project): RawApiDocsPage {
+ console.log(`- Utilities`);
+
+ return {
+ title: 'Utilities',
+ camelTitle: 'utils',
+ category: undefined,
+ deprecated: undefined,
+ description: 'A list of all the utilities available in Faker.js.',
+ examples: [],
+ methods: processProjectFunctions(project, 'mergeLocales'),
+ };
+}
+
+// Helpers
+
+function preparePage(
+ module: JSDocableLikeNode,
+ title: string,
+ category: string | undefined = undefined
+): RawApiDocsPage {
+ console.log(`- ${title}`);
+
+ const jsdocs = getJsDocs(module);
+
+ return {
+ title,
+ camelTitle: toCamelCase(title),
+ category,
+ deprecated: getDeprecated(jsdocs),
+ description: getDescription(jsdocs),
+ examples: getExamples(jsdocs),
+ methods: [],
+ };
+}
+
+function toCamelCase(value: string): string {
+ return value.substring(0, 1).toLowerCase() + value.substring(1);
+}
diff --git a/scripts/apidocs/processing/error.ts b/scripts/apidocs/processing/error.ts
new file mode 100644
index 00000000..f171d6b4
--- /dev/null
+++ b/scripts/apidocs/processing/error.ts
@@ -0,0 +1,40 @@
+import { FakerError } from '../../../src/errors/faker-error';
+import type { SourceableNode } from './source';
+import { getSourcePath } from './source';
+
+export class FakerApiDocsProcessingError extends FakerError {
+ constructor(options: {
+ type: string;
+ name: string;
+ source: string | SourceableNode;
+ cause: unknown;
+ }) {
+ const { type, name, source, cause } = options;
+ const sourceText =
+ typeof source === 'string' ? source : getSourcePathText(source);
+ const causeText = cause instanceof Error ? cause.message : '';
+ super(`Failed to process ${type} ${name} at ${sourceText} : ${causeText}`, {
+ cause,
+ });
+ }
+}
+
+export function newProcessingError(options: {
+ type: string;
+ name: string;
+ source: string | SourceableNode;
+ cause: unknown;
+}): FakerApiDocsProcessingError {
+ const { cause } = options;
+
+ if (cause instanceof FakerApiDocsProcessingError) {
+ return cause;
+ }
+
+ return new FakerApiDocsProcessingError(options);
+}
+
+function getSourcePathText(source: SourceableNode): string {
+ const { filePath, line, column } = getSourcePath(source);
+ return `${filePath}:${line}:${column}`;
+}
diff --git a/scripts/apidocs/processing/jsdocs.ts b/scripts/apidocs/processing/jsdocs.ts
new file mode 100644
index 00000000..4a8e5b08
--- /dev/null
+++ b/scripts/apidocs/processing/jsdocs.ts
@@ -0,0 +1,91 @@
+import type { JSDoc, JSDocTag, JSDocableNode } from 'ts-morph';
+import { JSDocParameterTag, JSDocTemplateTag } from 'ts-morph';
+import {
+ allRequired,
+ exactlyOne,
+ optionalOne,
+ required,
+} from '../utils/value-checks';
+
+export type JSDocableLikeNode = Pick<JSDocableNode, 'getJsDocs'>;
+
+export function getJsDocs(node: JSDocableLikeNode): JSDoc {
+ return exactlyOne(node.getJsDocs(), 'jsdocs');
+}
+
+export function getDeprecated(jsdocs: JSDoc): string | undefined {
+ return getOptionalTagFromJSDoc(jsdocs, 'deprecated');
+}
+
+export function getDescription(jsdocs: JSDoc | JSDocTag): string {
+ return required(jsdocs.getCommentText(), 'jsdocs description');
+}
+
+export function getSince(jsdocs: JSDoc): string {
+ return getExactlyOneTagFromJSDoc(jsdocs, 'since');
+}
+
+export function getTypeParameterTags(jsdocs: JSDoc): Record<string, JSDocTag> {
+ return Object.fromEntries(
+ jsdocs
+ .getTags()
+ .filter((tag) => tag.getTagName() === 'template')
+ .filter((tag) => tag instanceof JSDocTemplateTag)
+ .map((tag) => tag as JSDocTemplateTag)
+ .map((tag) => [tag.getTypeParameters()[0].getName(), tag] as const)
+ );
+}
+
+export function getParameterTags(jsdocs: JSDoc): Record<string, JSDocTag> {
+ return Object.fromEntries(
+ jsdocs
+ .getTags()
+ .filter((tag) => tag.getTagName() === 'param')
+ .filter((tag) => tag instanceof JSDocParameterTag)
+ .map((tag) => tag as JSDocParameterTag)
+ .map((tag) => [tag.getName(), tag] as const)
+ );
+}
+
+export function getDefault(jsdocs: JSDoc): string | undefined {
+ return getOptionalTagFromJSDoc(jsdocs, `default`);
+}
+
+export function getThrows(jsdocs: JSDoc): string[] {
+ return getTagsFromJSDoc(jsdocs, 'throws');
+}
+
+export function getExamples(jsdocs: JSDoc): string[] {
+ return getTagsFromJSDoc(jsdocs, 'example');
+}
+
+export function getSeeAlsos(jsdocs: JSDoc): string[] {
+ return getTagsFromJSDoc(jsdocs, 'see', true);
+}
+
+function getOptionalTagFromJSDoc(
+ jsdocs: JSDoc,
+ type: string
+): string | undefined {
+ return optionalOne(getTagsFromJSDoc(jsdocs, type), `@${type}`);
+}
+
+function getExactlyOneTagFromJSDoc(jsdocs: JSDoc, type: string): string {
+ return exactlyOne(getTagsFromJSDoc(jsdocs, type), `@${type}`);
+}
+
+function getTagsFromJSDoc(
+ jsdocs: JSDoc,
+ type: string,
+ full: boolean = false
+): string[] {
+ return allRequired(
+ jsdocs
+ .getTags()
+ .filter((tag) => tag.getTagName() === type)
+ .map((tag) =>
+ full ? tag.getStructure().text?.toString() : tag.getCommentText()
+ ),
+ `@${type}`
+ );
+}
diff --git a/scripts/apidocs/processing/method.ts b/scripts/apidocs/processing/method.ts
new file mode 100644
index 00000000..b9d1aa38
--- /dev/null
+++ b/scripts/apidocs/processing/method.ts
@@ -0,0 +1,197 @@
+import type {
+ ClassDeclaration,
+ FunctionDeclaration,
+ InterfaceDeclaration,
+ MethodSignature,
+ Project,
+} from 'ts-morph';
+import {
+ SyntaxKind,
+ type ConstructorDeclaration,
+ type MethodDeclaration,
+} from 'ts-morph';
+import { groupBy } from '../../../src/internal/group-by';
+import { valuesForKeys } from '../utils/value-checks';
+import { newProcessingError } from './error';
+import type {
+ RawApiDocsSignature,
+ SignatureLikeDeclaration,
+} from './signature';
+import { processSignatures } from './signature';
+import type { RawApiDocsSource } from './source';
+import { getSourcePath as getSource } from './source';
+
+/**
+ * Represents a method in the raw API docs.
+ */
+export interface RawApiDocsMethod {
+ /**
+ * The name of the method.
+ */
+ name: string;
+ /**
+ * The signatures of the method.
+ */
+ signatures: RawApiDocsSignature[];
+ /**
+ * The source of the method.
+ */
+ source: RawApiDocsSource;
+}
+
+// Constructors
+
+export function processClassConstructors(
+ clazz: ClassDeclaration
+): RawApiDocsMethod[] {
+ return processConstructors(clazz.getConstructors());
+}
+
+function processConstructors(
+ constructors: ConstructorDeclaration[]
+): RawApiDocsMethod[] {
+ return processMethodLikes(constructors, () => 'constructor');
+}
+
+// Class Methods
+
+export function processClassMethods(
+ clazz: ClassDeclaration
+): RawApiDocsMethod[] {
+ return processMethods(getAllMethods(clazz));
+}
+
+function getAllMethods(clazz: ClassDeclaration): MethodDeclaration[] {
+ const parents: ClassDeclaration[] = [clazz];
+ let parent: ClassDeclaration | undefined = clazz;
+ while ((parent = parent.getBaseClass()) != null) {
+ parents.unshift(parent);
+ }
+
+ const methods: Record<string, MethodDeclaration> = {};
+
+ for (const parent of parents) {
+ for (const method of parent.getMethods()) {
+ methods[method.getName()] = method;
+ }
+ }
+
+ return Object.values(methods).sort((a, b) =>
+ a.getName().localeCompare(b.getName())
+ );
+}
+
+type NamedMethodLikeDeclaration = MethodLikeDeclaration &
+ Pick<MethodDeclaration, 'getName'>;
+
+function processMethods(
+ methods: NamedMethodLikeDeclaration[]
+): RawApiDocsMethod[] {
+ return processMethodLikes(methods, (v) => v.getName());
+}
+
+// Interface Methods
+
+export function processInterfaceMethods(
+ iface: InterfaceDeclaration
+): RawApiDocsMethod[] {
+ return processMethodSignatures(iface.getMethods());
+}
+
+function processMethodSignatures(
+ methods: MethodSignature[]
+): RawApiDocsMethod[] {
+ const groupedSignatures = groupBy(methods, (v) => v.getName());
+
+ const methodLikes: NamedMethodLikeDeclaration[] = Object.values(
+ groupedSignatures
+ ).map((signatures) => {
+ const signature = signatures[0];
+
+ return {
+ getName: () => signature.getName(),
+ hasModifier: () => false,
+ getOverloads: () => signatures,
+ getTypeParameters: () => signature.getTypeParameters(),
+ getParameters: () => signature.getParameters(),
+ getReturnType: () => signature.getReturnType(),
+ getJsDocs: () => signature.getJsDocs(),
+ getSourceFile: () => signature.getSourceFile(),
+ getStart: () => signature.getStart(),
+ getText: () => signature.getText(),
+ };
+ });
+
+ return processMethods(methodLikes);
+}
+
+// Functions
+
+function getAllFunctions(
+ project: Project
+): Record<string, FunctionDeclaration> {
+ return Object.fromEntries(
+ project
+ .getSourceFiles()
+ .flatMap((file) => file.getFunctions())
+ .map((fn) => [fn.getNameOrThrow(), fn] as const)
+ );
+}
+
+export function processProjectFunctions(
+ project: Project,
+ ...names: string[]
+): RawApiDocsMethod[] {
+ return processMethodLikes(
+ valuesForKeys(getAllFunctions(project), names),
+ (f) => f.getNameOrThrow()
+ );
+}
+
+// Method-likes
+
+type MethodLikeDeclaration = SignatureLikeDeclaration &
+ Pick<MethodDeclaration, 'hasModifier'> & {
+ getOverloads(): SignatureLikeDeclaration[];
+ };
+
+function processMethodLikes<T extends MethodLikeDeclaration>(
+ methods: T[],
+ nameResolver: (value: T) => string
+): RawApiDocsMethod[] {
+ return methods
+ .filter((method) => !method.hasModifier(SyntaxKind.PrivateKeyword))
+ .map((method) => {
+ const name = nameResolver(method);
+ try {
+ return processMethodLike(name, method);
+ } catch (error) {
+ throw newProcessingError({
+ type: 'method',
+ name,
+ source: method,
+ cause: error,
+ });
+ }
+ })
+ .sort((a, b) => a.name.localeCompare(b.name));
+}
+
+export function processMethodLike(
+ name: string,
+ method: MethodLikeDeclaration
+): RawApiDocsMethod {
+ console.log(` - ${name}`);
+ const overloads = method.getOverloads();
+ const signatureData: SignatureLikeDeclaration[] =
+ overloads.length > 0 ? overloads : [method];
+
+ const signatures = processSignatures(name, signatureData, method);
+ const source = getSource(method);
+
+ return {
+ name,
+ signatures,
+ source,
+ };
+}
diff --git a/scripts/apidocs/processing/parameter.ts b/scripts/apidocs/processing/parameter.ts
new file mode 100644
index 00000000..7a6b67da
--- /dev/null
+++ b/scripts/apidocs/processing/parameter.ts
@@ -0,0 +1,203 @@
+import type {
+ PropertySignature,
+ Type,
+ TypeParameterDeclaration,
+} from 'ts-morph';
+import { type JSDoc, type JSDocTag, type ParameterDeclaration } from 'ts-morph';
+import { exactlyOne, valueForKey } from '../utils/value-checks';
+import { newProcessingError } from './error';
+import {
+ getDefault,
+ getDeprecated,
+ getDescription,
+ getJsDocs,
+ getParameterTags,
+ getTypeParameterTags,
+} from './jsdocs';
+import type { RawApiDocsType } from './type';
+import { getNameSuffix, getTypeText, isOptionsLikeType } from './type';
+
+/**
+ * Represents a parameter in the raw API docs.
+ */
+export interface RawApiDocsParameter {
+ /**
+ * The name of the parameter.
+ */
+ name: string;
+ /**
+ * The type of the parameter.
+ */
+ type: RawApiDocsType;
+ /**
+ * The default value or expression of the parameter, if it has one.
+ */
+ default: string | undefined;
+ /**
+ * The description of the parameter.
+ */
+ description: string;
+}
+
+export function processTypeParameters(
+ parameters: TypeParameterDeclaration[],
+ jsdocs: JSDoc
+): RawApiDocsParameter[] {
+ const paramTags = getTypeParameterTags(jsdocs);
+
+ return parameters.flatMap((parameter) => {
+ try {
+ return processTypeParameterEntry(parameter, paramTags);
+ } catch (error) {
+ throw newProcessingError({
+ type: 'type parameter',
+ name: parameter.getName(),
+ source: parameter,
+ cause: error,
+ });
+ }
+ });
+}
+
+function processTypeParameterEntry(
+ parameter: TypeParameterDeclaration,
+ paramTags: Record<string, JSDocTag>
+): RawApiDocsParameter {
+ return {
+ name: `<${parameter.getName()}>`,
+ type: getTypeText(parameter.getType(), { resolveAliases: true }),
+ default: parameter.getDefault()?.getText(),
+ description: getDescription(valueForKey(paramTags, parameter.getName())),
+ };
+}
+
+export function processParameters(
+ signatureParameters: ParameterDeclaration[],
+ implParameters: ParameterDeclaration[],
+ jsdocs: JSDoc
+): RawApiDocsParameter[] {
+ const paramTags = getParameterTags(jsdocs);
+ const implParameterDefaults = Object.fromEntries(
+ implParameters.map((parameter) => [
+ parameter.getName(),
+ getDefaultValue(parameter),
+ ])
+ );
+
+ return signatureParameters.flatMap((parameter) => {
+ try {
+ return processParameter(
+ parameter,
+ paramTags,
+ implParameterDefaults[parameter.getName()]
+ );
+ } catch (error) {
+ throw newProcessingError({
+ type: 'parameter',
+ name: parameter.getName(),
+ source: parameter,
+ cause: error,
+ });
+ }
+ });
+}
+
+function processParameter(
+ parameter: ParameterDeclaration,
+ paramTags: Record<string, JSDocTag>,
+ implementationDefault: string | undefined
+): RawApiDocsParameter[] {
+ const name = parameter.getName();
+ return [
+ processSimpleParameter(
+ parameter,
+ valueForKey(paramTags, name),
+ implementationDefault
+ ),
+ ...processComplexParameter(name, parameter.getType()),
+ ];
+}
+
+type ParameterLikeDeclaration = Pick<
+ ParameterDeclaration,
+ 'getName' | 'getType'
+> &
+ Partial<Pick<ParameterDeclaration, 'getInitializer'>>;
+
+function processSimpleParameter(
+ parameter: ParameterLikeDeclaration,
+ jsdocTag: JSDocTag,
+ implementationDefault: string | undefined
+): RawApiDocsParameter {
+ const name = parameter.getName();
+ const type = parameter.getType();
+ return {
+ name: `${name}${getNameSuffix(type)}`,
+ type: getTypeText(type, {
+ abbreviate: true,
+ stripUndefined: true,
+ }),
+ default: getDefaultValue(parameter) ?? implementationDefault,
+ description: getDescription(jsdocTag),
+ };
+}
+
+function getDefaultValue(
+ parameter: ParameterLikeDeclaration
+): string | undefined {
+ return parameter
+ .getInitializer?.()
+ ?.getText()
+ .replace(/ as .+$/, '');
+}
+
+function processComplexParameter(
+ name: string,
+ type: Type
+): RawApiDocsParameter[] {
+ if (type.isNullable()) {
+ return processComplexParameter(name, type.getNonNullableType());
+ } else if (type.isUnion()) {
+ return type
+ .getUnionTypes()
+ .flatMap((unionType) => processComplexParameter(name, unionType));
+ } else if (type.isArray()) {
+ return processComplexParameter(
+ `${name}[]`,
+ type.getArrayElementTypeOrThrow()
+ );
+ } else if (type.isObject()) {
+ if (!isOptionsLikeType(type)) {
+ return [];
+ }
+
+ return type
+ .getApparentProperties()
+ .flatMap((parameter) => {
+ const declaration = exactlyOne(
+ parameter.getDeclarations(),
+ 'property declaration'
+ ) as PropertySignature;
+ const propertyType = declaration.getType();
+ const jsdocs = getJsDocs(declaration);
+ const deprecated = getDeprecated(jsdocs);
+
+ return [
+ {
+ name: `${name}.${parameter.getName()}${getNameSuffix(propertyType)}`,
+ type: getTypeText(propertyType, {
+ abbreviate: false,
+ stripUndefined: true,
+ }),
+ default: getDefault(jsdocs),
+ description:
+ getDescription(jsdocs) +
+ (deprecated ? `\n\n**DEPRECATED:** ${deprecated}` : ''),
+ },
+ ];
+ })
+ .sort((a, b) => a.name.localeCompare(b.name));
+ }
+
+ return [];
+}
diff --git a/scripts/apidocs/processing/signature.ts b/scripts/apidocs/processing/signature.ts
new file mode 100644
index 00000000..c7ae872b
--- /dev/null
+++ b/scripts/apidocs/processing/signature.ts
@@ -0,0 +1,158 @@
+import type { MethodDeclaration } from 'ts-morph';
+import { getProject } from '../project';
+import { exactlyOne } from '../utils/value-checks';
+import { newProcessingError } from './error';
+import type { JSDocableLikeNode } from './jsdocs';
+import {
+ getDeprecated,
+ getDescription,
+ getExamples,
+ getJsDocs,
+ getSeeAlsos,
+ getSince,
+ getThrows,
+} from './jsdocs';
+import type { RawApiDocsParameter } from './parameter';
+import { processParameters, processTypeParameters } from './parameter';
+import type { SourceableNode } from './source';
+import type { RawApiDocsType } from './type';
+import { getTypeText } from './type';
+
+/**
+ * Represents a method signature in the raw API docs.
+ */
+export interface RawApiDocsSignature {
+ /**
+ * The deprecation notice of the signature, if it has one.
+ */
+ deprecated: string | undefined;
+ /**
+ * The description of the signature.
+ */
+ description: string;
+ /**
+ * The version when the signature was added.
+ */
+ since: string;
+ /**
+ * The parameters of the signature.
+ */
+ parameters: RawApiDocsParameter[];
+ /**
+ * The return type of the signature.
+ */
+ returns: RawApiDocsType;
+ /**
+ * The exceptions thrown by the signature.
+ */
+ throws: string[];
+ /**
+ * The full call signature as text.
+ */
+ signature: string;
+ /**
+ * The usage examples of the signature.
+ */
+ examples: string[];
+ /**
+ * The see also links of the signature.
+ */
+ seeAlsos: string[];
+}
+
+export type SignatureLikeDeclaration = Pick<
+ MethodDeclaration,
+ 'getTypeParameters' | 'getParameters' | 'getReturnType' | 'getText'
+> &
+ JSDocableLikeNode &
+ SourceableNode;
+
+export function processSignatures(
+ name: string,
+ signatures: SignatureLikeDeclaration[],
+ implementation: SignatureLikeDeclaration
+): RawApiDocsSignature[] {
+ return signatures.map((signature, i) => {
+ try {
+ return processSignature(signature, implementation);
+ } catch (error) {
+ throw newProcessingError({
+ type: 'signature',
+ name: `${name}/${i}`,
+ source: signature,
+ cause: error,
+ });
+ }
+ });
+}
+
+function processSignature(
+ signature: SignatureLikeDeclaration,
+ implementation: SignatureLikeDeclaration
+): RawApiDocsSignature {
+ const jsdocs = getJsDocs(signature);
+ const parameters = [
+ ...processTypeParameters(signature.getTypeParameters(), jsdocs),
+ ...processParameters(
+ signature.getParameters(),
+ implementation.getParameters(),
+ jsdocs
+ ),
+ ];
+ const returns = getTypeText(signature.getReturnType());
+
+ try {
+ return {
+ deprecated: getDeprecated(jsdocs),
+ description: getDescription(jsdocs),
+ since: getSince(jsdocs),
+ parameters,
+ returns,
+ throws: getThrows(jsdocs),
+ signature: getSignatureText(signature),
+ examples: getExamples(jsdocs),
+ seeAlsos: getSeeAlsos(jsdocs),
+ };
+ } catch (error) {
+ throw newProcessingError({
+ type: 'jsdocs',
+ name: signature.getText(),
+ source: jsdocs,
+ cause: error,
+ });
+ }
+}
+
+// Cache the project for performance reasons
+const signatureExtractionProject = getProject({
+ skipAddingFilesFromTsConfig: true,
+});
+
+function getSignatureText(signature: SignatureLikeDeclaration): string {
+ const fullText = signature
+ .getText()
+ // Remove all jsdocs
+ .replaceAll(/ *\/\*\*[^\n]*\n(\s*\*[^\n]*\n)*\s*\*\/\n/g, '')
+ // Remove all empty lines
+ .replaceAll(/\n\n+/g, '\n')
+ // Remove the export function keyword for consistency with member methods
+ .replace(/^export function /, '');
+
+ // Is this already a signature
+ if (fullText.endsWith(';')) {
+ // Restore the function keyword
+ return `function ${fullText}`;
+ }
+
+ // Create a copy of the signature to keep the line numbers unchanged
+ // and for performance reasons, as removing and re-adding the body is slow.
+ // We use a function here to avoid unnecessary boilerplate
+ const fn = exactlyOne(
+ signatureExtractionProject
+ .createSourceFile('temp.ts', `function ${fullText}`, { overwrite: true })
+ .getFunctions(),
+ 'function signature'
+ );
+ fn.removeBody();
+ return fn.getText();
+}
diff --git a/scripts/apidocs/processing/source.ts b/scripts/apidocs/processing/source.ts
new file mode 100644
index 00000000..ac23c9fb
--- /dev/null
+++ b/scripts/apidocs/processing/source.ts
@@ -0,0 +1,37 @@
+import type { Node } from 'ts-morph';
+import { FILE_PATH_PROJECT } from '../utils/paths';
+
+/**
+ * Represents a source element in the raw API docs.
+ */
+export interface RawApiDocsSource {
+ /**
+ * The file path of the target element.
+ */
+ filePath: string;
+ /**
+ * The line number of the target element.
+ */
+ line: number;
+ /**
+ * The column number of the target element.
+ */
+ column: number;
+}
+
+export type SourceableNode = Pick<Node, 'getSourceFile' | 'getStart'>;
+
+export function getSourcePath(node: SourceableNode): RawApiDocsSource {
+ const sourceFile = node.getSourceFile();
+ const filePath = sourceFile
+ .getFilePath()
+ .substring(FILE_PATH_PROJECT.length + 1);
+ const startPosition = node.getStart();
+ const { line, column } = sourceFile.getLineAndColumnAtPos(startPosition);
+
+ return {
+ filePath,
+ line,
+ column,
+ };
+}
diff --git a/scripts/apidocs/processing/type.ts b/scripts/apidocs/processing/type.ts
new file mode 100644
index 00000000..25ec30c9
--- /dev/null
+++ b/scripts/apidocs/processing/type.ts
@@ -0,0 +1,231 @@
+import { TypeFlags, type Type } from 'ts-morph';
+import { atLeastOneAndAllRequired, required } from '../utils/value-checks';
+
+export type RawApiDocsType =
+ | RawApiDocsSimpleType
+ | RawApiDocsGenericType
+ | RawApiDocsUnionType
+ | RawApiDocsShadowType;
+
+interface RawApiDocsBaseType {
+ type: string;
+ text: string;
+}
+
+export interface RawApiDocsSimpleType extends RawApiDocsBaseType {
+ type: 'simple';
+}
+
+export interface RawApiDocsGenericType extends RawApiDocsBaseType {
+ type: 'generic';
+ typeParameters: RawApiDocsType[];
+}
+
+export interface RawApiDocsUnionType extends RawApiDocsBaseType {
+ type: 'union';
+ types: RawApiDocsType[];
+}
+
+export interface RawApiDocsShadowType extends RawApiDocsBaseType {
+ type: 'shadow';
+ resolvedType: RawApiDocsType;
+}
+
+export function getNameSuffix(type: Type): string {
+ return type.isNullable() ? '?' : '';
+}
+
+export function getTypeText(
+ type: Type,
+ options: {
+ abbreviate?: boolean;
+ stripUndefined?: boolean;
+ resolveAliases?: boolean;
+ } = {}
+): RawApiDocsType {
+ const {
+ abbreviate = false,
+ stripUndefined = false,
+ resolveAliases = false,
+ } = options;
+
+ if (
+ type.isAny() ||
+ type.isUnknown() ||
+ type.isBoolean() ||
+ type.isBooleanLiteral() ||
+ type.isNumber() ||
+ type.isNumberLiteral() ||
+ type.getFlags() & TypeFlags.BigInt ||
+ type.getFlags() & TypeFlags.ESSymbol ||
+ type.isString() ||
+ type.isUndefined() ||
+ type.isNull() ||
+ type.isVoid() ||
+ type.isNever()
+ ) {
+ return newSimpleType(type.getText());
+ } else if (type.isStringLiteral()) {
+ return newSimpleType(type.getText().replace(/^"(.*)"$/, "'$1'"));
+ } else if (type.isArray()) {
+ return newArrayType(
+ getTypeText(type.getArrayElementTypeOrThrow(), options)
+ );
+ } else if (stripUndefined && type.isNullable()) {
+ return getTypeText(type.getNonNullableType(), options);
+ }
+
+ const symbol = type.getSymbol() ?? type.getAliasSymbol();
+ if (!resolveAliases && symbol) {
+ const name = symbol.getName();
+ if (name !== '__type') {
+ const typeArguments = [
+ ...type.getTypeArguments(),
+ ...type.getAliasTypeArguments(),
+ ];
+
+ if (name === 'LiteralUnion') {
+ const displayType = getTypeText(typeArguments[0], options);
+ const baseType = typeArguments[1]
+ ? getTypeText(typeArguments[1], options)
+ : newSimpleType('string');
+
+ return newUnionType([displayType, baseType]);
+ }
+
+ const typeParameters = typeArguments.map((t) => getTypeText(t, options));
+
+ if (typeParameters.length === 0) {
+ const resolvedType = getTypeText(type, {
+ ...options,
+ resolveAliases: true,
+ });
+
+ if (name === resolvedType.text) {
+ return newSimpleType(name);
+ }
+
+ return newShadowType(name, resolvedType);
+ }
+
+ return newGenericType(name, typeParameters);
+ }
+ }
+
+ if (type.isUnion()) {
+ let unionTypes = type
+ .getUnionTypes()
+ .map((unionType) => getTypeText(unionType, options))
+ .filter((unionType) => !stripUndefined || unionType.text !== 'undefined');
+
+ const trueIndex = unionTypes.findIndex(
+ (unionType) => unionType.text === 'true'
+ );
+ if (
+ trueIndex !== -1 &&
+ unionTypes.some((unionType) => unionType.text === 'false')
+ ) {
+ unionTypes[trueIndex] = newSimpleType('boolean');
+ unionTypes = unionTypes.filter(
+ (unionType) => unionType.text !== 'true' && unionType.text !== 'false'
+ );
+ }
+
+ if (unionTypes.length === 1) {
+ return unionTypes[0];
+ }
+
+ return newUnionType(unionTypes);
+ }
+
+ if (abbreviate && isOptionsLikeType(type)) {
+ return newSimpleType('{ ... }');
+ }
+
+ if (resolveAliases && type.isTypeParameter()) {
+ const text = getTypeText(type.getApparentType(), {
+ ...options,
+ resolveAliases: true,
+ });
+
+ if (text.text === 'unknown') {
+ return newSimpleType('any');
+ }
+
+ return text;
+ }
+
+ return newSimpleType(type.getText().replaceAll(/import\([^)]*\)\./g, ''));
+}
+
+export function isOptionsLikeType(type: Type): boolean {
+ return (
+ type.isObject() &&
+ type.isAnonymous() &&
+ type.getCallSignatures().length === 0 &&
+ type.getTupleElements().length === 0
+ );
+}
+
+function newSimpleType(name: string): RawApiDocsSimpleType {
+ required(name, 'name');
+ return { type: 'simple', text: name };
+}
+
+function newArrayType(typeParameter: RawApiDocsType): RawApiDocsGenericType {
+ const { text } = required(typeParameter, 'array type');
+ const useGeneric = text.includes('|') || text.includes('{');
+ return {
+ type: 'generic',
+ typeParameters: [typeParameter],
+ text: useGeneric ? `Array<${text}>` : `${text}[]`,
+ };
+}
+
+function newGenericType(
+ name: string,
+ typeParameters: RawApiDocsType[]
+): RawApiDocsType {
+ required(name, 'name');
+ atLeastOneAndAllRequired(typeParameters, 'type parameters');
+ return {
+ type: 'generic',
+ typeParameters,
+ text: `${name}<${typeParameters.map((t) => t.text).join(', ')}>`,
+ };
+}
+
+function newUnionType(types: RawApiDocsType[]): RawApiDocsUnionType {
+ atLeastOneAndAllRequired(types, 'unions');
+ return {
+ type: 'union',
+ types,
+ text: types
+ .map((type) => type.text)
+ .map((text) =>
+ // Remove LiteralUnion shadow types
+ text.endsWith(' & { zz_IGNORE_ME?: undefined; }')
+ ? text.slice(0, -32)
+ : text
+ )
+ .map((text) => {
+ // () => T -> (() => T)
+ const isFunctionSignature = text.startsWith('(');
+ return isFunctionSignature ? `(${text})` : text;
+ })
+ .join(' | '),
+ };
+}
+
+function newShadowType(
+ displayText: string,
+ resolvedType: RawApiDocsType
+): RawApiDocsShadowType {
+ required(displayText, 'display text');
+ required(resolvedType, 'resolved type');
+ return {
+ type: 'shadow',
+ resolvedType,
+ text: displayText,
+ };
+}
diff --git a/scripts/apidocs/project.ts b/scripts/apidocs/project.ts
new file mode 100644
index 00000000..c63bad8e
--- /dev/null
+++ b/scripts/apidocs/project.ts
@@ -0,0 +1,9 @@
+import type { ProjectOptions } from 'ts-morph';
+import { Project } from 'ts-morph';
+
+export function getProject(options: Partial<ProjectOptions> = {}): Project {
+ return new Project({
+ ...options,
+ tsConfigFilePath: options.tsConfigFilePath ?? 'tsconfig.build.json',
+ });
+}
diff --git a/scripts/apidoc/format.ts b/scripts/apidocs/utils/format.ts
index a8f63ac0..ab9f524d 100644
--- a/scripts/apidoc/format.ts
+++ b/scripts/apidocs/utils/format.ts
@@ -1,6 +1,6 @@
import type { Options } from 'prettier';
import { format } from 'prettier';
-import prettierConfig from '../../.prettierrc.js';
+import prettierConfig from '../../../.prettierrc.js';
/**
* Formats markdown contents.
diff --git a/scripts/apidoc/markdown.ts b/scripts/apidocs/utils/markdown.ts
index 0b1e3b40..b0cc68be 100644
--- a/scripts/apidoc/markdown.ts
+++ b/scripts/apidocs/utils/markdown.ts
@@ -1,14 +1,14 @@
import sanitizeHtml from 'sanitize-html';
import type { MarkdownRenderer } from 'vitepress';
import { createMarkdownRenderer } from 'vitepress';
-import vitepressConfig from '../../docs/.vitepress/config';
-import { adjustUrls, pathOutputDir } from './utils';
+import vitepressConfig from '../../../docs/.vitepress/config';
+import { FILE_PATH_API_DOCS } from './paths';
let markdown: MarkdownRenderer;
export async function initMarkdownRenderer(): Promise<void> {
markdown = await createMarkdownRenderer(
- pathOutputDir,
+ FILE_PATH_API_DOCS,
vitepressConfig.markdown,
'/'
);
@@ -31,7 +31,7 @@ const htmlSanitizeOptions: sanitizeHtml.IOptions = {
a: ['href', 'target', 'rel'],
button: ['class', 'title'],
div: ['class'],
- pre: ['class', 'tabindex', 'v-pre'],
+ pre: ['class', 'v-pre'],
span: ['class', 'style'],
},
selfClosing: [],
@@ -45,7 +45,6 @@ function comparableSanitizedHtml(html: string): string {
.replaceAll('&gt;', '>')
.replaceAll('&lt;', '<')
.replaceAll('&amp;', '&')
- .replaceAll('&quot;', '"')
.replaceAll('=""', '')
.replaceAll(' ', '');
}
@@ -70,7 +69,27 @@ export function codeToHtml(code: string): string {
*
* @returns The converted HTML string.
*/
-export function mdToHtml(md: string, inline: boolean = false): string {
+export function mdToHtml(md: string, inline?: boolean): string;
+/**
+ * Converts Markdown to an HTML string and sanitizes it.
+ *
+ * @param md The markdown to convert.
+ * @param inline Whether to render the markdown as inline, without a wrapping `<p>` tag. Defaults to `false`.
+ *
+ * @returns The converted HTML string.
+ */
+export function mdToHtml(
+ md: string | undefined,
+ inline?: boolean
+): string | undefined;
+export function mdToHtml(
+ md: string | undefined,
+ inline: boolean = false
+): string | undefined {
+ if (md == null) {
+ return undefined;
+ }
+
const rawHtml = inline ? markdown.renderInline(md) : markdown.render(md);
const safeHtml: string = sanitizeHtml(rawHtml, htmlSanitizeOptions);
@@ -79,9 +98,14 @@ export function mdToHtml(md: string, inline: boolean = false): string {
return adjustUrls(safeHtml);
}
- console.debug('Rejected unsafe md:', md);
- console.error('Rejected unsafe html:', rawHtml);
- console.error('Rejected unsafe html:', comparableSanitizedHtml(rawHtml));
- console.error('Expected safe html:', comparableSanitizedHtml(safeHtml));
+ console.debug('Rejected unsafe md:\n', md);
+ console.error('Rejected unsafe html:\n', rawHtml);
+ console.error('Clean unsafe html:\n', comparableSanitizedHtml(rawHtml));
+ console.error('Clean safe html:\n', comparableSanitizedHtml(safeHtml));
+ console.log('-'.repeat(80));
throw new Error('Found unsafe html');
}
+
+export function adjustUrls(description: string): string {
+ return description.replaceAll(/https:\/\/(next.)?fakerjs.dev\//g, '/');
+}
diff --git a/scripts/apidocs/utils/paths.ts b/scripts/apidocs/utils/paths.ts
new file mode 100644
index 00000000..8abca1ea
--- /dev/null
+++ b/scripts/apidocs/utils/paths.ts
@@ -0,0 +1,24 @@
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const FILE_PATH_THIS = dirname(fileURLToPath(import.meta.url));
+/**
+ * The path to the project directory.
+ */
+// Required for converting the source file paths to relative paths
+export const FILE_PATH_PROJECT = resolve(FILE_PATH_THIS, '..', '..', '..');
+/**
+ * The path to the docs directory.
+ */
+// Required for writing the api page vitepress config
+export const FILE_PATH_DOCS = resolve(FILE_PATH_PROJECT, 'docs');
+/**
+ * The path to the website's public directory.
+ */
+// Required for publishing the diff index
+export const FILE_PATH_PUBLIC = resolve(FILE_PATH_DOCS, 'public');
+/**
+ * The path to the api docs directory.
+ */
+// Required for writing various api docs files
+export const FILE_PATH_API_DOCS = resolve(FILE_PATH_DOCS, 'api');
diff --git a/scripts/apidocs/utils/value-checks.ts b/scripts/apidocs/utils/value-checks.ts
new file mode 100644
index 00000000..f8578ccc
--- /dev/null
+++ b/scripts/apidocs/utils/value-checks.ts
@@ -0,0 +1,69 @@
+export function exactlyOne<T>(input: ReadonlyArray<T>, property: string): T {
+ if (input.length !== 1) {
+ throw new Error(
+ `Expected exactly one element for ${property}, got ${input.length}`
+ );
+ }
+
+ return input[0];
+}
+
+export function optionalOne<T>(
+ input: ReadonlyArray<T>,
+ property: string
+): T | undefined {
+ if (input.length > 1) {
+ throw new Error(
+ `Expected one optional element for ${property}, got ${input.length}`
+ );
+ }
+
+ return input[0];
+}
+
+export function required<T>(
+ input: T | undefined,
+ property: string
+): NonNullable<T> {
+ if (input == null) {
+ throw new Error(`Expected a value for ${property}, got undefined`);
+ }
+
+ return input;
+}
+
+export function allRequired<T>(
+ input: ReadonlyArray<T | undefined>,
+ property: string
+): Array<NonNullable<T>> {
+ return input.map((v, i) => required(v, `${property}[${i}]`));
+}
+
+export function atLeastOne<T>(
+ input: ReadonlyArray<T>,
+ property: string
+): ReadonlyArray<T> {
+ if (input.length === 0) {
+ throw new Error(`Expected at least one element for ${property}`);
+ }
+
+ return input;
+}
+
+export function atLeastOneAndAllRequired<T>(
+ input: ReadonlyArray<T | undefined>,
+ property: string
+): ReadonlyArray<NonNullable<T>> {
+ return atLeastOne(allRequired(input, property), property);
+}
+
+export function valueForKey<T>(input: Record<string, T>, key: string): T {
+ return required(input[key], key);
+}
+
+export function valuesForKeys<T>(
+ input: Record<string, T>,
+ keys: string[]
+): T[] {
+ return keys.map((key) => valueForKey(input, key));
+}
diff --git a/scripts/diff.ts b/scripts/diff.ts
index 813154f2..f52076aa 100644
--- a/scripts/diff.ts
+++ b/scripts/diff.ts
@@ -2,14 +2,14 @@
import { existsSync } from 'node:fs';
import { argv } from 'node:process';
-import { diff } from './apidoc/diff';
-import { pathDocsDiffIndexFile } from './apidoc/utils';
+import { diff } from './apidocs/diff';
+import { FILE_PATH_DOCS_DIFF_INDEX } from './apidocs/output/diff-index';
const [target, source] = argv.slice(2);
-if (!source && !existsSync(pathDocsDiffIndexFile)) {
+if (!source && !existsSync(FILE_PATH_DOCS_DIFF_INDEX)) {
throw new Error(
- `Unable to find local diff index file at: ${pathDocsDiffIndexFile}\n
+ `Unable to find local diff index file at: ${FILE_PATH_DOCS_DIFF_INDEX}\n
You can run \`pnpm run generate:api-docs\` to generate it.`
);
}
diff --git a/scripts/generate-locales.ts b/scripts/generate-locales.ts
index 90ea9a34..cba70edc 100644
--- a/scripts/generate-locales.ts
+++ b/scripts/generate-locales.ts
@@ -25,7 +25,7 @@ import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { LocaleDefinition, MetadataDefinition } from '../src/definitions';
import { keys } from '../src/internal/keys';
-import { formatMarkdown, formatTypescript } from './apidoc/format';
+import { formatMarkdown, formatTypescript } from './apidocs/utils/format';
// Constants
diff --git a/test/scripts/apidoc/__snapshots__/module.spec.ts.snap b/test/scripts/apidoc/__snapshots__/module.spec.ts.snap
deleted file mode 100644
index d4275860..00000000
--- a/test/scripts/apidoc/__snapshots__/module.spec.ts.snap
+++ /dev/null
@@ -1,54 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`module > analyzeModule() > ModuleDeprecationTest 1`] = `
-{
- "comment": "This is a description for a module with a code example.",
- "deprecated": "Well, this is deprecated.",
- "examples": undefined,
-}
-`;
-
-exports[`module > analyzeModule() > ModuleExampleTest 1`] = `
-{
- "comment": "This is a description for a module with a code example.",
- "deprecated": undefined,
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> ModuleExampleTest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
-</div>",
-}
-`;
-
-exports[`module > analyzeModule() > ModuleFakerJsLinkTest 1`] = `
-{
- "comment": "Description with a link to our [website](/)
-and [api docs](/api/).",
- "deprecated": undefined,
- "examples": undefined,
-}
-`;
-
-exports[`module > analyzeModule() > ModuleNextFakerJsLinkTest 1`] = `
-{
- "comment": "Description with a link to our [website](/)
-and [api docs](/api/).",
- "deprecated": undefined,
- "examples": undefined,
-}
-`;
-
-exports[`module > analyzeModule() > ModuleSimpleTest 1`] = `
-{
- "comment": "A simple module without anything special.",
- "deprecated": undefined,
- "examples": undefined,
-}
-`;
-
-exports[`module > analyzeModule() > expected and actual modules are equal 1`] = `
-[
- "ModuleDeprecationTest",
- "ModuleExampleTest",
- "ModuleFakerJsLinkTest",
- "ModuleNextFakerJsLinkTest",
- "ModuleSimpleTest",
-]
-`;
diff --git a/test/scripts/apidoc/__snapshots__/signature.spec.ts.snap b/test/scripts/apidoc/__snapshots__/signature.spec.ts.snap
deleted file mode 100644
index 37a9f754..00000000
--- a/test/scripts/apidoc/__snapshots__/signature.spec.ts.snap
+++ /dev/null
@@ -1,768 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`signature > analyzeSignature() > complexArrayParameter 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Complex array parameter.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">complexArrayParameter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&gt;(array: readonly </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">Array</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&lt;{</span></span>
-<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> T</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
-<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> weight</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> number</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}&gt;): </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">T</span></span></code></pre>
-</div>",
- "name": "complexArrayParameter",
- "parameters": [
- {
- "description": "<p>The type of the entries to pick from.</p>
-",
- "name": "<T>",
- "type": undefined,
- },
- {
- "default": undefined,
- "description": "<p>Array to pick the value from.</p>
-",
- "name": "array",
- "type": "Array<{ ... }>",
- },
- {
- "default": undefined,
- "description": "<p>The value to pick.</p>
-",
- "name": "array[].value",
- "type": "T",
- },
- {
- "default": undefined,
- "description": "<p>The weight of the value.</p>
-",
- "name": "array[].weight",
- "type": "number",
- },
- ],
- "returns": "T",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L377",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > defaultBooleanParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with a default parameter.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">defaultBooleanParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(c: boolean </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">): number</span></span></code></pre>
-</div>",
- "name": "defaultBooleanParamMethod",
- "parameters": [
- {
- "default": "true",
- "description": "<p>The boolean parameter.</p>
-",
- "name": "c",
- "type": "boolean",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L105",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > expected and actual methods are equal 1`] = `
-[
- "complexArrayParameter",
- "defaultBooleanParamMethod",
- "functionParamMethod",
- "literalUnionParamMethod",
- "methodWithDeprecated",
- "methodWithDeprecatedOption",
- "methodWithExample",
- "methodWithMultipleSeeMarkers",
- "methodWithMultipleSeeMarkersAndBackticks",
- "methodWithMultipleThrows",
- "methodWithSinceMarker",
- "methodWithThrows",
- "multiParamMethod",
- "noParamMethod",
- "optionalStringParamMethod",
- "optionsInlineParamMethodWithDefaults",
- "optionsInterfaceParamMethodWithDefaults",
- "optionsParamMethod",
- "optionsTypeParamMethodWithDefaults",
- "recordParamMethod",
- "requiredNumberParamMethod",
- "stringUnionParamMethod",
-]
-`;
-
-exports[`signature > analyzeSignature() > functionParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with a function parameters.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">functionParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(fn: (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">a</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> number): number</span></span></code></pre>
-</div>",
- "name": "functionParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p>The function parameter.</p>
-",
- "name": "fn",
- "type": "(a: string) => number",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L125",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > literalUnionParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with LiteralUnion.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">literalUnionParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(value: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, namedValue</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, array</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> readonly Array</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, namedArray</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> readonly Array</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, mixed</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> readonly Array</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, namedMixed</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> readonly Array</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> string</span></span></code></pre>
-</div>",
- "name": "literalUnionParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p><code>'a'</code> or <code>'b'</code>.</p>
-",
- "name": "value",
- "type": "'a' | 'b' | ?",
- },
- {
- "default": undefined,
- "description": "<p><code>'a'</code> or <code>'b'</code>.</p>
-",
- "name": "namedValue",
- "type": "'a' | 'b' | ?",
- },
- {
- "default": undefined,
- "description": "<p>Array of <code>'a'</code> or <code>'b'</code>.</p>
-",
- "name": "array",
- "type": "Array<'a' | 'b' | ?>",
- },
- {
- "default": undefined,
- "description": "<p>Array of <code>'a'</code> or <code>'b'</code>.</p>
-",
- "name": "namedArray",
- "type": "Array<'a' | 'b' | ?>",
- },
- {
- "default": undefined,
- "description": "<p>Value <code>'a'</code> or <code>'b'</code> or an array thereof.</p>
-",
- "name": "mixed",
- "type": "'a' | 'b' | ? | Array<'a' | 'b' | ?>",
- },
- {
- "default": undefined,
- "description": "<p>Value <code>'a'</code> or <code>'b'</code> or an array thereof.</p>
-",
- "name": "namedMixed",
- "type": "'a' | 'b' | ? | Array<'a' | 'b' | ?>",
- },
- ],
- "returns": "string",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L159",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithDeprecated 1`] = `
-{
- "deprecated": "<p>do something else</p>
-",
- "description": "<p>Test with deprecated and see marker.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithDeprecated</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span></code></pre>
-</div>",
- "name": "methodWithDeprecated",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [
- "test.apidoc.methodWithExample()",
- ],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L287",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithDeprecatedOption 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with deprecated option.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithDeprecatedOption</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(option: {</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a: string,</span></span>
-<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> number,</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> c: number</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}): number</span></span></code></pre>
-</div>",
- "name": "methodWithDeprecatedOption",
- "parameters": [
- {
- "default": undefined,
- "description": "<p>The options.</p>
-",
- "name": "option",
- "type": "{ ... }",
- },
- {
- "default": undefined,
- "description": "<p>Some deprecated option.</p>
-<p><strong>DEPRECATED:</strong> do something else.</p>
-",
- "name": "option.a",
- "type": "string",
- },
- {
- "default": undefined,
- "description": "<p>Some other deprecated option.</p>
-<p><strong>DEPRECATED:</strong> do something else.</p>
-",
- "name": "option.b",
- "type": "() => number",
- },
- {
- "default": undefined,
- "description": "<p>Some other option.</p>
-",
- "name": "option.c",
- "type": "number",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L318",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithExample 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with example marker.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">test.apidoc.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithExample</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 0</span></span></code></pre>
-</div>",
- "name": "methodWithExample",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L276",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithMultipleSeeMarkers 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with multiple see markers.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithMultipleSeeMarkers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span></code></pre>
-</div>",
- "name": "methodWithMultipleSeeMarkers",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [
- "test.apidoc.methodWithExample()",
- "test.apidoc.methodWithDeprecated()",
- ],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L345",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithMultipleSeeMarkersAndBackticks 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with multiple see markers and backticks.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithMultipleSeeMarkersAndBackticks</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span></code></pre>
-</div>",
- "name": "methodWithMultipleSeeMarkersAndBackticks",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [
- "test.apidoc.methodWithExample() with parameter <code>foo</code>.",
- "test.apidoc.methodWithDeprecated() with parameter <code>bar</code> and <code>baz</code>.",
- ],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L355",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithMultipleThrows 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with multiple throws.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithMultipleThrows</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span></code></pre>
-</div>",
- "name": "methodWithMultipleThrows",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L306",
- "throws": "First error case.
-Another error case.",
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithSinceMarker 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with since marker.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithSinceMarker</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span></code></pre>
-</div>",
- "name": "methodWithSinceMarker",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [],
- "since": "1.0.0",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L364",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > methodWithThrows 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with throws.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">methodWithThrows</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span></code></pre>
-</div>",
- "name": "methodWithThrows",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L296",
- "throws": "Everytime.",
-}
-`;
-
-exports[`signature > analyzeSignature() > multiParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with multiple parameters.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">multiParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a: number, b</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> string, c: boolean </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">): number</span></span></code></pre>
-</div>",
- "name": "multiParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p>The number parameter.</p>
-",
- "name": "a",
- "type": "number",
- },
- {
- "default": undefined,
- "description": "<p>The string parameter.</p>
-",
- "name": "b?",
- "type": "string",
- },
- {
- "default": "true",
- "description": "<p>The boolean parameter.</p>
-",
- "name": "c",
- "type": "boolean",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L116",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > noParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with no parameters.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">noParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(): number</span></span></code></pre>
-</div>",
- "name": "noParamMethod",
- "parameters": [],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L78",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > optionalStringParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with an optional parameter.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">optionalStringParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(b</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> string): number</span></span></code></pre>
-</div>",
- "name": "optionalStringParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p>The string parameter.</p>
-",
- "name": "b?",
- "type": "string",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L96",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > optionsInlineParamMethodWithDefaults 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with a function parameters (inline types) with defaults.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">optionsInlineParamMethodWithDefaults</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a: {</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value: number</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">} </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }, b: {</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value: number</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">} </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }, c: {</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> value: number</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}): number</span></span></code></pre>
-</div>",
- "name": "optionsInlineParamMethodWithDefaults",
- "parameters": [
- {
- "default": "{ value: 1 }",
- "description": "<p>Parameter with signature default.
-It also has a more complex description.</p>
-",
- "name": "a",
- "type": "{ ... }",
- },
- {
- "default": undefined,
- "description": "<p>The number parameter.</p>
-",
- "name": "a.value?",
- "type": "number",
- },
- {
- "default": "{ value: 1 }",
- "description": "<p>Parameter with jsdocs default.</p>
-<p>It also has a more complex description.</p>
-",
- "name": "b",
- "type": "{ ... }",
- },
- {
- "default": undefined,
- "description": "<p>The number parameter.</p>
-",
- "name": "b.value?",
- "type": "number",
- },
- {
- "default": undefined,
- "description": "<p>Parameter with inner jsdocs default.</p>
-",
- "name": "c",
- "type": "{ ... }",
- },
- {
- "default": "2",
- "description": "<p>The number parameter. It also has a more complex description.</p>
-",
- "name": "c.value?",
- "type": "number",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L226",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > optionsInterfaceParamMethodWithDefaults 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with a function parameters with defaults.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">optionsInterfaceParamMethodWithDefaults</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a: ParameterOptionsInterfaceA </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }, b: ParameterOptionsInterfaceB </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }, c: ParameterOptionsInterfaceC): number</span></span></code></pre>
-</div>",
- "name": "optionsInterfaceParamMethodWithDefaults",
- "parameters": [
- {
- "default": "{ value: 1 }",
- "description": "<p>Parameter with signature default.</p>
-",
- "name": "a",
- "type": "ParameterOptionsInterfaceA",
- },
- {
- "default": "{ value: 1 }",
- "description": "<p>Parameter with jsdocs default.</p>
-",
- "name": "b",
- "type": "ParameterOptionsInterfaceB",
- },
- {
- "default": undefined,
- "description": "<p>Parameter with inner jsdocs default.</p>
-",
- "name": "c",
- "type": "ParameterOptionsInterfaceC",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L262",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > optionsParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with an options parameter.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">optionsParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(options: {</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> a: number,</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> b: string,</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> c: boolean,</span></span>
-<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> d</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> string,</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> e: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ?</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">})</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> number</span></span></code></pre>
-</div>",
- "name": "optionsParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p>The options parameter.</p>
-",
- "name": "options",
- "type": "{ ... }",
- },
- {
- "default": undefined,
- "description": "<p>The number parameter.</p>
-",
- "name": "options.a",
- "type": "number",
- },
- {
- "default": undefined,
- "description": "<p>The string parameter.</p>
-",
- "name": "options.b?",
- "type": "string",
- },
- {
- "default": undefined,
- "description": "<p>The boolean parameter.</p>
-",
- "name": "options.c",
- "type": "boolean",
- },
- {
- "default": undefined,
- "description": "<p>The method parameter.</p>
-",
- "name": "options.d",
- "type": "() => string",
- },
- {
- "default": "'a'",
- "description": "<p>A parameter with inline documentation.</p>
-",
- "name": "options.e",
- "type": "'a' | 'b' | ?",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L196",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > optionsTypeParamMethodWithDefaults 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with a function parameters with defaults.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">optionsTypeParamMethodWithDefaults</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a: ParameterOptionsTypeA </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }, b: ParameterOptionsTypeB </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { value: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }, c: ParameterOptionsTypeC): number</span></span></code></pre>
-</div>",
- "name": "optionsTypeParamMethodWithDefaults",
- "parameters": [
- {
- "default": "{ value: 1 }",
- "description": "<p>Parameter with signature default.</p>
-",
- "name": "a",
- "type": "ParameterOptionsTypeA",
- },
- {
- "default": "{ value: 1 }",
- "description": "<p>Parameter with jsdocs default.</p>
-",
- "name": "b",
- "type": "ParameterOptionsTypeB",
- },
- {
- "default": undefined,
- "description": "<p>Parameter with inner jsdocs default.</p>
-",
- "name": "c",
- "type": "ParameterOptionsTypeC",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L244",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > recordParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with a Record parameter.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">recordParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(object: Record</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&lt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">string, number</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">): number</span></span></code></pre>
-</div>",
- "name": "recordParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p>The Record parameter.</p>
-",
- "name": "object",
- "type": "Record<string, number>",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L182",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > requiredNumberParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with a required parameter.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">requiredNumberParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(a: number): number</span></span></code></pre>
-</div>",
- "name": "requiredNumberParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p>The number parameter.</p>
-",
- "name": "a",
- "type": "number",
- },
- ],
- "returns": "number",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L87",
- "throws": undefined,
-}
-`;
-
-exports[`signature > analyzeSignature() > stringUnionParamMethod 1`] = `
-{
- "deprecated": undefined,
- "description": "<p>Test with string union.</p>
-",
- "examples": "<div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" v-pre><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">stringUnionParamMethod</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(value: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'a'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'b'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, options</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">?:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> casing: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'lower'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'mixed'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'upper'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> excludes: readonly AlphaNumericChar[],</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> format: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'binary'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'css'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'decimal'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> |</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'hex'</span></span>
-<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}): string</span></span></code></pre>
-</div>",
- "name": "stringUnionParamMethod",
- "parameters": [
- {
- "default": undefined,
- "description": "<p><code>'a'</code> or <code>'b'</code>.</p>
-",
- "name": "value",
- "type": "'a' | 'b'",
- },
- {
- "default": undefined,
- "description": "<p>The options parameter.</p>
-",
- "name": "options?",
- "type": "{ ... }",
- },
- {
- "default": undefined,
- "description": "<p>The casing parameter.</p>
-",
- "name": "options.casing?",
- "type": "'lower' | 'mixed' | 'upper'",
- },
- {
- "default": undefined,
- "description": "<p>The excludes parameter.</p>
-",
- "name": "options.excludes?",
- "type": "readonly AlphaNumericChar[]",
- },
- {
- "default": undefined,
- "description": "<p>The format parameter.</p>
-",
- "name": "options.format?",
- "type": "'binary' | 'css' | 'decimal' | 'hex'",
- },
- ],
- "returns": "string",
- "seeAlsos": [],
- "since": "Missing",
- "sourcePath": "test/scripts/apidoc/signature.example.ts#L138",
- "throws": undefined,
-}
-`;
diff --git a/test/scripts/apidoc/module.spec.ts b/test/scripts/apidoc/module.spec.ts
deleted file mode 100644
index 5e55afea..00000000
--- a/test/scripts/apidoc/module.spec.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { beforeAll, describe, expect, it } from 'vitest';
-import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown';
-import { analyzeModule } from '../../../scripts/apidoc/module-methods';
-import * as ModuleTests from './module.example';
-import { loadExampleModules } from './utils';
-
-beforeAll(initMarkdownRenderer);
-const modules = await loadExampleModules();
-
-describe('module', () => {
- describe('analyzeModule()', () => {
- it('dummy dependency to rerun the test if the example changes', () => {
- expect(Object.keys(ModuleTests)).not.toEqual([]);
- });
-
- it('expected and actual modules are equal', () => {
- expect(Object.keys(modules).sort()).toMatchSnapshot();
- });
-
- it.each(Object.entries(modules))('%s', (_, module) => {
- const actual = analyzeModule(module);
-
- expect(actual).toMatchSnapshot();
- });
- });
-});
diff --git a/test/scripts/apidoc/signature.debug.ts b/test/scripts/apidoc/signature.debug.ts
deleted file mode 100644
index 704b629c..00000000
--- a/test/scripts/apidoc/signature.debug.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * This file exists, because vitest doesn't allow me to debug code outside of src and test.
- * And it's easier to test these features independently from the main project.
- */
-import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown';
-import { analyzeSignature } from '../../../scripts/apidoc/signature';
-import { loadExampleMethods } from './utils';
-
-/* Run with `pnpm tsx test/scripts/apidoc/signature.debug.ts` */
-
-await initMarkdownRenderer();
-const methods = await loadExampleMethods();
-for (const [name, method] of Object.entries(methods)) {
- console.log('Analyzing:', name);
- const result = await analyzeSignature(method, '', method.name);
- console.log('Result:', result);
-}
diff --git a/test/scripts/apidoc/signature.spec.ts b/test/scripts/apidoc/signature.spec.ts
deleted file mode 100644
index 51935fcf..00000000
--- a/test/scripts/apidoc/signature.spec.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { beforeAll, describe, expect, it } from 'vitest';
-import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown';
-import { analyzeSignature } from '../../../scripts/apidoc/signature';
-import { SignatureTest } from './signature.example';
-import { loadExampleMethods } from './utils';
-
-beforeAll(initMarkdownRenderer);
-const methods = await loadExampleMethods();
-
-describe('signature', () => {
- describe('analyzeSignature()', () => {
- it('dummy dependency to rerun the test if the example changes', () => {
- expect(new SignatureTest()).toBeTruthy();
- });
-
- it('expected and actual methods are equal', () => {
- expect(Object.keys(methods)).toMatchSnapshot();
- });
-
- it.each(Object.entries(methods))('%s', async (name, signature) => {
- const actual = await analyzeSignature(signature, '', name);
-
- expect(actual).toMatchSnapshot();
- });
- });
-});
diff --git a/test/scripts/apidoc/utils.ts b/test/scripts/apidoc/utils.ts
deleted file mode 100644
index 2752f25b..00000000
--- a/test/scripts/apidoc/utils.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import type {
- DeclarationReflection,
- SignatureReflection,
- TypeDocOptions,
-} from 'typedoc';
-import {
- loadProject,
- selectApiMethodSignatures,
- selectApiModules,
-} from '../../../scripts/apidoc/typedoc';
-import { mapByName } from '../../../scripts/apidoc/utils';
-
-/**
- * Returns a record with the (Module-Name -> (Method-Name -> Method-Signature)) for the project.
- *
- * @param options The TypeDoc options.
- * @param includeTestModules Whether to include the test modules.
- */
-export async function loadProjectModules(
- options?: Partial<TypeDocOptions>,
- includeTestModules = false
-): Promise<
- Record<string, [DeclarationReflection, Record<string, SignatureReflection>]>
-> {
- const [, project] = await loadProject(options);
-
- const modules = selectApiModules(project, includeTestModules);
-
- return mapByName(modules, (m) => [m, selectApiMethodSignatures(m)]);
-}
-
-/**
- * Loads the example methods using TypeDoc.
- */
-export async function loadExampleMethods(): Promise<
- Record<string, SignatureReflection>
-> {
- const modules = await loadProjectModules(
- {
- entryPoints: ['test/scripts/apidoc/signature.example.ts'],
- },
- true
- );
- return modules['SignatureTest'][1];
-}
-
-/**
- * Loads the example modules using TypeDoc.
- */
-export async function loadExampleModules(): Promise<
- Record<string, DeclarationReflection>
-> {
- const modules = await loadProjectModules(
- {
- entryPoints: ['test/scripts/apidoc/module.example.ts'],
- },
- true
- );
-
- const result: Record<string, DeclarationReflection> = {};
- for (const key in modules) {
- result[key] = modules[key][0];
- }
-
- return result;
-}
diff --git a/test/scripts/apidoc/verify-jsdoc-tags.spec.ts b/test/scripts/apidoc/verify-jsdoc-tags.spec.ts
deleted file mode 100644
index da34316e..00000000
--- a/test/scripts/apidoc/verify-jsdoc-tags.spec.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
-import { dirname, resolve } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import type { ReflectionType, SomeType } from 'typedoc';
-import validator from 'validator';
-import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
-import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown';
-import { analyzeSignature } from '../../../scripts/apidoc/signature';
-import {
- MISSING_DESCRIPTION,
- extractDeprecated,
- extractDescription,
- extractJoinedRawExamples,
- extractModuleFieldName,
- extractRawDefault,
- extractSeeAlsos,
- extractSince,
- extractSummaryDefault,
- extractTagContent,
-} from '../../../scripts/apidoc/typedoc';
-import { loadProjectModules } from './utils';
-
-// This test ensures, that every method
-// - has working examples
-// - running these do not log anything, unless the method is deprecated
-
-beforeAll(initMarkdownRenderer);
-
-const tempDir = resolve(dirname(fileURLToPath(import.meta.url)), 'temp');
-
-afterAll(() => {
- // Remove temp folder
- if (existsSync(tempDir)) {
- rmSync(tempDir, { recursive: true });
- }
-});
-
-const modules = await loadProjectModules();
-
-function resolveDirToModule(moduleName: string): string {
- return resolve(tempDir, moduleName);
-}
-
-function resolvePathToMethodFile(
- moduleName: string,
- methodName: string
-): string {
- const dir = resolveDirToModule(moduleName);
- return resolve(dir, `${methodName}.ts`);
-}
-
-const allowedReferences = new Set(
- Object.values(modules).flatMap(([module, methods]) => {
- const moduleFieldName = extractModuleFieldName(module);
- return Object.keys(methods).map(
- (methodName) => `faker.${moduleFieldName}.${methodName}`
- );
- })
-);
-const allowedLinks = new Set(
- Object.values(modules).flatMap(([module, methods]) => {
- const moduleFieldName = extractModuleFieldName(module);
- return [
- `/api/${moduleFieldName}.html`,
- ...Object.keys(methods).map(
- (methodName) =>
- `/api/${moduleFieldName}.html#${methodName.toLowerCase()}`
- ),
- ];
- })
-);
-
-function assertDescription(description: string, isHtml: boolean): void {
- const linkRegexp = isHtml ? /(href)="([^"]+)"/g : /\[([^\]]+)\]\(([^)]+)\)/g;
- const links = [...description.matchAll(linkRegexp)].map((m) => m[2]);
-
- for (const link of links) {
- if (!isHtml) {
- expect(link).toMatch(/^https?:\/\//);
- expect(link).toSatisfy(validator.isURL);
- }
-
- if (isHtml ? link.startsWith('/api/') : link.includes('fakerjs.dev/api/')) {
- expect(allowedLinks, `${link} to point to a valid target`).toContain(
- link.replace(/.*fakerjs.dev\//, '/')
- );
- }
- }
-}
-
-// keep in sync with analyzeParameterOptions
-function assertNestedParameterDefault(
- name: string,
- parameterType?: SomeType
-): void {
- if (!parameterType) {
- return;
- }
-
- switch (parameterType.type) {
- case 'array': {
- return assertNestedParameterDefault(
- `${name}[]`,
- parameterType.elementType
- );
- }
-
- case 'union': {
- for (const type of parameterType.types) {
- assertNestedParameterDefault(name, type);
- }
-
- return;
- }
-
- case 'reflection': {
- for (const property of parameterType.declaration.children ?? []) {
- const reflection = property.comment
- ? property
- : (property.type as ReflectionType)?.declaration?.signatures?.[0];
- const comment = reflection?.comment;
- const tagDefault = extractRawDefault({ comment }) || undefined;
- const summaryDefault = extractSummaryDefault(comment, false);
-
- if (summaryDefault) {
- expect(
- tagDefault,
- `Expect jsdoc summary default and @default for ${name}.${property.name} to be the same`
- ).toBe(summaryDefault);
- }
- }
-
- return;
- }
-
- case 'typeOperator': {
- return assertNestedParameterDefault(name, parameterType.target);
- }
-
- default: {
- return;
- }
- }
-}
-
-describe('verify JSDoc tags', () => {
- describe.each(Object.entries(modules))(
- '%s',
- (moduleName, [module, methodsByName]) => {
- describe('verify module', () => {
- it('verify description', () => {
- const description = extractDescription(module);
- assertDescription(description, false);
- });
- });
-
- describe.each(Object.entries(methodsByName))(
- '%s',
- (methodName, signature) => {
- beforeAll(() => {
- // Write temp files to disk
-
- // Extract examples and make them runnable
- const examples = extractJoinedRawExamples(signature) ?? '';
-
- // Save examples to a file to run them later in the specific tests
- const dir = resolveDirToModule(moduleName);
- mkdirSync(dir, { recursive: true });
-
- const path = resolvePathToMethodFile(moduleName, methodName);
- const imports = [
- ...new Set(examples.match(/(?<!\.)faker[^.]*(?=\.)/g)),
- ];
- writeFileSync(
- path,
- `import { ${imports.join(
- ', '
- )} } from '../../../../../src';\n\n${examples}`
- );
- });
-
- it('verify description', () => {
- const description = extractDescription(signature);
- assertDescription(description, false);
- });
-
- it('verify @example tag', async () => {
- // Extract the examples
- const examples = extractJoinedRawExamples(signature);
-
- expect(
- examples,
- `${moduleName}.${methodName} to have examples`
- ).not.toBe('');
-
- // Grab path to example file
- const path = resolvePathToMethodFile(moduleName, methodName);
-
- // Executing the examples should not throw
- await expect(
- import(`${path}?scope=example`)
- ).resolves.toBeDefined();
- });
-
- // This only checks whether the whole method is deprecated or not
- // It does not check whether the method is deprecated for a specific set of arguments
- it('verify @deprecated tag', async () => {
- // Grab path to example file
- const path = resolvePathToMethodFile(moduleName, methodName);
-
- const consoleWarnSpy = vi.spyOn(console, 'warn');
-
- // Run the examples
- await import(`${path}?scope=deprecated`);
-
- // Verify that deprecated methods log a warning
- const deprecatedFlag = extractDeprecated(signature) !== undefined;
- if (deprecatedFlag) {
- expect(consoleWarnSpy).toHaveBeenCalled();
- expect(
- extractTagContent('@deprecated', signature).join(''),
- '@deprecated tag without message'
- ).not.toBe('');
- } else {
- expect(consoleWarnSpy).not.toHaveBeenCalled();
- }
- });
-
- it('verify @param tags', async () => {
- // This must run before analyzeSignature
- for (const param of signature.parameters ?? []) {
- const type = param.type;
- const paramDefault = param.defaultValue;
- const commentDefault = extractSummaryDefault(
- param.comment,
- false
- );
- if (paramDefault) {
- if (
- /^{.*}$/.test(paramDefault) ||
- paramDefault.includes('\n')
- ) {
- expect(commentDefault).toBeUndefined();
- } else {
- expect(
- commentDefault,
- `Expect '${param.name}'s js implementation default to be the same as the jsdoc summary default.`
- ).toBe(paramDefault);
- }
- }
-
- assertNestedParameterDefault(param.name, type);
- }
-
- for (const param of (
- await analyzeSignature(signature, '', methodName)
- ).parameters) {
- const { name, description } = param;
- const plainDescription = description
- .replaceAll(/<[^>]+>/g, '')
- .trim();
- expect(
- plainDescription,
- `Expect param ${name} to have a description`
- ).not.toBe(MISSING_DESCRIPTION);
- assertDescription(description, true);
- }
- });
-
- it('verify @see tags', () => {
- for (const link of extractSeeAlsos(signature)) {
- if (link.startsWith('faker.')) {
- // Expected @see faker.xxx.yyy()
- expect(link, 'Expect method reference to contain ()').toContain(
- '('
- );
- expect(link, 'Expect method reference to contain ()').toContain(
- ')'
- );
- expect(
- link,
- "Expect method reference to have a ': ' after the parenthesis"
- ).toContain('): ');
- expect(
- link,
- 'Expect method reference to have a description starting with a capital letter'
- ).toMatch(/\): [A-Z]/);
- expect(
- link,
- 'Expect method reference to start with a standard description phrase'
- ).toMatch(
- /\): (?:For generating |For more information about |For using |For the replacement method)/
- );
- expect(
- link,
- 'Expect method reference to have a description ending with a dot'
- ).toMatch(/\.$/);
- expect(allowedReferences).toContain(link.replace(/\(.*/, ''));
- }
- }
- });
-
- it('verify @since tag', () => {
- const since = extractSince(signature);
- expect(since, '@since to be present').toBeTruthy();
- expect(since).not.toBe(MISSING_DESCRIPTION);
- expect(since, '@since to be a valid semver').toSatisfy(
- validator.isSemVer
- );
- });
- }
- );
- }
- );
-});
diff --git a/test/scripts/apidoc/.gitignore b/test/scripts/apidocs/.gitignore
index a6d7ecd9..a6d7ecd9 100644
--- a/test/scripts/apidoc/.gitignore
+++ b/test/scripts/apidocs/.gitignore
diff --git a/test/scripts/apidocs/__snapshots__/class.spec.ts.snap b/test/scripts/apidocs/__snapshots__/class.spec.ts.snap
new file mode 100644
index 00000000..6461169d
--- /dev/null
+++ b/test/scripts/apidocs/__snapshots__/class.spec.ts.snap
@@ -0,0 +1,75 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`class > expected and actual modules are equal 1`] = `
+[
+ "ModuleDeprecationTest",
+ "ModuleExampleTest",
+ "ModuleFakerJsLinkTest",
+ "ModuleNextFakerJsLinkTest",
+ "ModuleSimpleTest",
+]
+`;
+
+exports[`class > processClass(ModuleDeprecationTest) 1`] = `
+{
+ "camelTitle": "moduleDeprecationTest",
+ "category": undefined,
+ "deprecated": "Well, this is deprecated.",
+ "description": "This is a description for a module with a code example.",
+ "examples": [],
+ "methods": [],
+ "title": "ModuleDeprecationTest",
+}
+`;
+
+exports[`class > processClass(ModuleExampleTest) 1`] = `
+{
+ "camelTitle": "moduleExampleTest",
+ "category": undefined,
+ "deprecated": undefined,
+ "description": "This is a description for a module with a code example.",
+ "examples": [
+ "new ModuleExampleTest()",
+ ],
+ "methods": [],
+ "title": "ModuleExampleTest",
+}
+`;
+
+exports[`class > processClass(ModuleFakerJsLinkTest) 1`] = `
+{
+ "camelTitle": "moduleFakerJsLinkTest",
+ "category": undefined,
+ "deprecated": undefined,
+ "description": "Description with a link to our [website](https://fakerjs.dev/)
+and [api docs](https://fakerjs.dev/api/).",
+ "examples": [],
+ "methods": [],
+ "title": "ModuleFakerJsLinkTest",
+}
+`;
+
+exports[`class > processClass(ModuleNextFakerJsLinkTest) 1`] = `
+{
+ "camelTitle": "moduleNextFakerJsLinkTest",
+ "category": undefined,
+ "deprecated": undefined,
+ "description": "Description with a link to our [website](https://next.fakerjs.dev/)
+and [api docs](https://next.fakerjs.dev/api/).",
+ "examples": [],
+ "methods": [],
+ "title": "ModuleNextFakerJsLinkTest",
+}
+`;
+
+exports[`class > processClass(ModuleSimpleTest) 1`] = `
+{
+ "camelTitle": "moduleSimpleTest",
+ "category": undefined,
+ "deprecated": undefined,
+ "description": "A simple module without anything special.",
+ "examples": [],
+ "methods": [],
+ "title": "ModuleSimpleTest",
+}
+`;
diff --git a/test/scripts/apidocs/__snapshots__/method.spec.ts.snap b/test/scripts/apidocs/__snapshots__/method.spec.ts.snap
new file mode 100644
index 00000000..5a5518e4
--- /dev/null
+++ b/test/scripts/apidocs/__snapshots__/method.spec.ts.snap
@@ -0,0 +1,1671 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`method > expected and actual methods are equal 1`] = `
+[
+ "complexArrayParameter",
+ "defaultBooleanParamMethod",
+ "functionParamMethod",
+ "literalUnionParamMethod",
+ "methodWithDeprecated",
+ "methodWithDeprecatedOption",
+ "methodWithExample",
+ "methodWithMultipleSeeMarkers",
+ "methodWithMultipleSeeMarkersAndBackticks",
+ "methodWithMultipleThrows",
+ "methodWithSinceMarker",
+ "methodWithThrows",
+ "multiParamMethod",
+ "noParamMethod",
+ "optionalStringParamMethod",
+ "optionsInlineParamMethodWithDefaults",
+ "optionsInterfaceParamMethodWithDefaults",
+ "optionsParamMethod",
+ "optionsTypeParamMethodWithDefaults",
+ "recordParamMethod",
+ "requiredNumberParamMethod",
+ "stringUnionParamMethod",
+]
+`;
+
+exports[`method > processMethodLike(complexArrayParameter) 1`] = `
+{
+ "name": "complexArrayParameter",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Complex array parameter.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The type of the entries to pick from.",
+ "name": "<T>",
+ "type": {
+ "text": "any",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Array to pick the value from.",
+ "name": "array",
+ "type": {
+ "text": "Array<{ ... }>",
+ "type": "generic",
+ "typeParameters": [
+ {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The value to pick.",
+ "name": "array[].value",
+ "type": {
+ "resolvedType": {
+ "text": "any",
+ "type": "simple",
+ },
+ "text": "T",
+ "type": "shadow",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The weight of the value.",
+ "name": "array[].weight",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "resolvedType": {
+ "text": "any",
+ "type": "simple",
+ },
+ "text": "T",
+ "type": "shadow",
+ },
+ "seeAlsos": [],
+ "signature": "function complexArrayParameter<T>(
+ array: ReadonlyArray<{
+ weight: number;
+ value: T;
+ }>
+ ): T;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(defaultBooleanParamMethod) 1`] = `
+{
+ "name": "defaultBooleanParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with a default parameter.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": "true",
+ "description": "The boolean parameter.",
+ "name": "c",
+ "type": {
+ "text": "boolean",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function defaultBooleanParamMethod(c: boolean = true): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(functionParamMethod) 1`] = `
+{
+ "name": "functionParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with a function parameters.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The function parameter.",
+ "name": "fn",
+ "type": {
+ "text": "(a: string) => number",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function functionParamMethod(fn: (a: string) => number): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(literalUnionParamMethod) 1`] = `
+{
+ "name": "literalUnionParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with LiteralUnion.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "\`'a'\` or \`'b'\`.",
+ "name": "value",
+ "type": {
+ "text": "'a' | 'b' | string",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ {
+ "text": "string",
+ "type": "simple",
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "\`'a'\` or \`'b'\`.",
+ "name": "namedValue",
+ "type": {
+ "text": "AB | string",
+ "type": "union",
+ "types": [
+ {
+ "resolvedType": {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ "text": "AB",
+ "type": "shadow",
+ },
+ {
+ "text": "string",
+ "type": "simple",
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Array of \`'a'\` or \`'b'\`.",
+ "name": "array",
+ "type": {
+ "text": "Array<'a' | 'b' | string>",
+ "type": "generic",
+ "typeParameters": [
+ {
+ "text": "'a' | 'b' | string",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ {
+ "text": "string",
+ "type": "simple",
+ },
+ ],
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Array of \`'a'\` or \`'b'\`.",
+ "name": "namedArray",
+ "type": {
+ "text": "Array<AB | string>",
+ "type": "generic",
+ "typeParameters": [
+ {
+ "text": "AB | string",
+ "type": "union",
+ "types": [
+ {
+ "resolvedType": {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ "text": "AB",
+ "type": "shadow",
+ },
+ {
+ "text": "string",
+ "type": "simple",
+ },
+ ],
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Value \`'a'\` or \`'b'\` or an array thereof.",
+ "name": "mixed",
+ "type": {
+ "text": "'a' | 'b' | string | Array<'a' | 'b' | string>",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ {
+ "text": "string & { zz_IGNORE_ME?: undefined; }",
+ "type": "simple",
+ },
+ {
+ "text": "Array<'a' | 'b' | string>",
+ "type": "generic",
+ "typeParameters": [
+ {
+ "text": "'a' | 'b' | string",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ {
+ "text": "string",
+ "type": "simple",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Value \`'a'\` or \`'b'\` or an array thereof.",
+ "name": "namedMixed",
+ "type": {
+ "text": "'a' | 'b' | string | Array<AB | string>",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ {
+ "text": "string & { zz_IGNORE_ME?: undefined; }",
+ "type": "simple",
+ },
+ {
+ "text": "Array<AB | string>",
+ "type": "generic",
+ "typeParameters": [
+ {
+ "text": "AB | string",
+ "type": "union",
+ "types": [
+ {
+ "resolvedType": {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ "text": "AB",
+ "type": "shadow",
+ },
+ {
+ "text": "string",
+ "type": "simple",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ },
+ ],
+ "returns": {
+ "text": "string",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function literalUnionParamMethod(
+ value: LiteralUnion<'a' | 'b'>,
+ namedValue: LiteralUnion<AB>,
+ array: ReadonlyArray<LiteralUnion<'a' | 'b'>>,
+ namedArray: ReadonlyArray<LiteralUnion<AB>>,
+ mixed: LiteralUnion<'a' | 'b'> | ReadonlyArray<LiteralUnion<'a' | 'b'>>,
+ namedMixed: ReadonlyArray<LiteralUnion<AB>> | LiteralUnion<AB>
+ ): string;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithDeprecated) 1`] = `
+{
+ "name": "methodWithDeprecated",
+ "signatures": [
+ {
+ "deprecated": "do something else",
+ "description": "Test with deprecated and see marker.",
+ "examples": [],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [
+ "test.apidocs.methodWithExample()",
+ ],
+ "signature": "function methodWithDeprecated(): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithDeprecatedOption) 1`] = `
+{
+ "name": "methodWithDeprecatedOption",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with deprecated option.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The options.",
+ "name": "option",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Some deprecated option.
+
+**DEPRECATED:** do something else.",
+ "name": "option.a",
+ "type": {
+ "text": "string",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Some other deprecated option.
+
+**DEPRECATED:** do something else.",
+ "name": "option.b",
+ "type": {
+ "text": "() => number",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Some other option.",
+ "name": "option.c",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function methodWithDeprecatedOption(option: {
+ a: string;
+ b: () => number;
+ c: number;
+ }): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithExample) 1`] = `
+{
+ "name": "methodWithExample",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with example marker.",
+ "examples": [
+ "test.apidocs.methodWithExample() // 0",
+ ],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function methodWithExample(): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithMultipleSeeMarkers) 1`] = `
+{
+ "name": "methodWithMultipleSeeMarkers",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with multiple see markers.",
+ "examples": [],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [
+ "test.apidocsmethodWithExample()",
+ "test.apidocsmethodWithDeprecated()",
+ ],
+ "signature": "function methodWithMultipleSeeMarkers(): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithMultipleSeeMarkersAndBackticks) 1`] = `
+{
+ "name": "methodWithMultipleSeeMarkersAndBackticks",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with multiple see markers and backticks.",
+ "examples": [],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [
+ "test.apidocsmethodWithExample() with parameter \`foo\`.",
+ "test.apidocsmethodWithDeprecated() with parameter \`bar\` and \`baz\`.",
+ ],
+ "signature": "function methodWithMultipleSeeMarkersAndBackticks(): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithMultipleThrows) 1`] = `
+{
+ "name": "methodWithMultipleThrows",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with multiple throws.",
+ "examples": [],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function methodWithMultipleThrows(): number;",
+ "since": "1.0.0",
+ "throws": [
+ "First error case.",
+ "Another error case.",
+ ],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithSinceMarker) 1`] = `
+{
+ "name": "methodWithSinceMarker",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with since marker.",
+ "examples": [],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function methodWithSinceMarker(): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(methodWithThrows) 1`] = `
+{
+ "name": "methodWithThrows",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with throws.",
+ "examples": [],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function methodWithThrows(): number;",
+ "since": "1.0.0",
+ "throws": [
+ "Everytime.",
+ ],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(multiParamMethod) 1`] = `
+{
+ "name": "multiParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with multiple parameters.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The number parameter.",
+ "name": "a",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The string parameter.",
+ "name": "b?",
+ "type": {
+ "text": "string",
+ "type": "simple",
+ },
+ },
+ {
+ "default": "true",
+ "description": "The boolean parameter.",
+ "name": "c",
+ "type": {
+ "text": "boolean",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function multiParamMethod(a: number, b?: string, c: boolean = true): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(noParamMethod) 1`] = `
+{
+ "name": "noParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with no parameters.",
+ "examples": [],
+ "parameters": [],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function noParamMethod(): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(optionalStringParamMethod) 1`] = `
+{
+ "name": "optionalStringParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with an optional parameter.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The string parameter.",
+ "name": "b?",
+ "type": {
+ "text": "string",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function optionalStringParamMethod(b?: string): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(optionsInlineParamMethodWithDefaults) 1`] = `
+{
+ "name": "optionsInlineParamMethodWithDefaults",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with a function parameters (inline types) with defaults.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": "{ value: 1 }",
+ "description": "Parameter with signature default.
+It also has a more complex description.",
+ "name": "a",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The number parameter.",
+ "name": "a.value?",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Parameter with jsdocs default.
+
+It also has a more complex description.
+
+Defaults to \`{ value: 1 }\`.",
+ "name": "b",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The number parameter.",
+ "name": "b.value?",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Parameter with inner jsdocs default.",
+ "name": "c",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The number parameter.",
+ "name": "c.value?",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function optionsInlineParamMethodWithDefaults(
+ a: {
+ value?: number;
+ } = { value: 1 },
+ b: {
+ value?: number;
+ },
+ c: {
+ value?: number;
+ }
+ ): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(optionsInterfaceParamMethodWithDefaults) 1`] = `
+{
+ "name": "optionsInterfaceParamMethodWithDefaults",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with a function parameters with defaults.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": "{ value: 1 }",
+ "description": "Parameter with signature default.",
+ "name": "a",
+ "type": {
+ "text": "ParameterOptionsInterfaceA",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Parameter with jsdocs default. Defaults to \`{ value: 1 }\`.",
+ "name": "b",
+ "type": {
+ "text": "ParameterOptionsInterfaceB",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Parameter with inner jsdocs default.",
+ "name": "c",
+ "type": {
+ "text": "ParameterOptionsInterfaceC",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function optionsInterfaceParamMethodWithDefaults(
+ a: ParameterOptionsInterfaceA = { value: 1 },
+ b: ParameterOptionsInterfaceB,
+ c: ParameterOptionsInterfaceC
+ ): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(optionsParamMethod) 1`] = `
+{
+ "name": "optionsParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with an options parameter.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The options parameter.",
+ "name": "options",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The number parameter.",
+ "name": "options.a",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The string parameter.",
+ "name": "options.b?",
+ "type": {
+ "text": "string",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The boolean parameter.",
+ "name": "options.c",
+ "type": {
+ "text": "boolean",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The method parameter.",
+ "name": "options.d",
+ "type": {
+ "text": "() => string",
+ "type": "simple",
+ },
+ },
+ {
+ "default": "'a'",
+ "description": "A parameter with inline documentation.",
+ "name": "options.e",
+ "type": {
+ "text": "'a' | 'b' | string",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ {
+ "text": "string",
+ "type": "simple",
+ },
+ ],
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function optionsParamMethod(options: {
+ a: number;
+ b?: string;
+ c: boolean;
+ d: () => string;
+ e: LiteralUnion<'a' | 'b'>;
+ }): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(optionsTypeParamMethodWithDefaults) 1`] = `
+{
+ "name": "optionsTypeParamMethodWithDefaults",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with a function parameters with defaults.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": "{ value: 1 }",
+ "description": "Parameter with signature default.",
+ "name": "a",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Options value.",
+ "name": "a.value?",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Parameter with jsdocs default. Defaults to \`{ value: 1 }\`.",
+ "name": "b",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Options value.",
+ "name": "b.value?",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Parameter with inner jsdocs default.",
+ "name": "c",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "Options value. Defaults to \`0\`.",
+ "name": "c.value?",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function optionsTypeParamMethodWithDefaults(
+ a: ParameterOptionsTypeA = { value: 1 },
+ b: ParameterOptionsTypeB,
+ c: ParameterOptionsTypeC
+ ): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(recordParamMethod) 1`] = `
+{
+ "name": "recordParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with a Record parameter.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The Record parameter.",
+ "name": "object",
+ "type": {
+ "text": "Record<string, number>",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function recordParamMethod(object: Record<string, number>): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(requiredNumberParamMethod) 1`] = `
+{
+ "name": "requiredNumberParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with a required parameter.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "The number parameter.",
+ "name": "a",
+ "type": {
+ "text": "number",
+ "type": "simple",
+ },
+ },
+ ],
+ "returns": {
+ "text": "number",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function requiredNumberParamMethod(a: number): number;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
+
+exports[`method > processMethodLike(stringUnionParamMethod) 1`] = `
+{
+ "name": "stringUnionParamMethod",
+ "signatures": [
+ {
+ "deprecated": undefined,
+ "description": "Test with string union.",
+ "examples": [],
+ "parameters": [
+ {
+ "default": undefined,
+ "description": "\`'a'\` or \`'b'\`.",
+ "name": "value",
+ "type": {
+ "text": "'a' | 'b'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The options parameter.",
+ "name": "options?",
+ "type": {
+ "text": "{ ... }",
+ "type": "simple",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The casing parameter.",
+ "name": "options.casing?",
+ "type": {
+ "resolvedType": {
+ "text": "'lower' | 'upper' | 'mixed'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'lower'",
+ "type": "simple",
+ },
+ {
+ "text": "'upper'",
+ "type": "simple",
+ },
+ {
+ "text": "'mixed'",
+ "type": "simple",
+ },
+ ],
+ },
+ "text": "Casing",
+ "type": "shadow",
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The excludes parameter.",
+ "name": "options.excludes?",
+ "type": {
+ "text": "AlphaNumericChar[]",
+ "type": "generic",
+ "typeParameters": [
+ {
+ "resolvedType": {
+ "text": "'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'a'",
+ "type": "simple",
+ },
+ {
+ "text": "'b'",
+ "type": "simple",
+ },
+ {
+ "text": "'c'",
+ "type": "simple",
+ },
+ {
+ "text": "'d'",
+ "type": "simple",
+ },
+ {
+ "text": "'e'",
+ "type": "simple",
+ },
+ {
+ "text": "'f'",
+ "type": "simple",
+ },
+ {
+ "text": "'g'",
+ "type": "simple",
+ },
+ {
+ "text": "'h'",
+ "type": "simple",
+ },
+ {
+ "text": "'i'",
+ "type": "simple",
+ },
+ {
+ "text": "'j'",
+ "type": "simple",
+ },
+ {
+ "text": "'k'",
+ "type": "simple",
+ },
+ {
+ "text": "'l'",
+ "type": "simple",
+ },
+ {
+ "text": "'m'",
+ "type": "simple",
+ },
+ {
+ "text": "'n'",
+ "type": "simple",
+ },
+ {
+ "text": "'o'",
+ "type": "simple",
+ },
+ {
+ "text": "'p'",
+ "type": "simple",
+ },
+ {
+ "text": "'q'",
+ "type": "simple",
+ },
+ {
+ "text": "'r'",
+ "type": "simple",
+ },
+ {
+ "text": "'s'",
+ "type": "simple",
+ },
+ {
+ "text": "'t'",
+ "type": "simple",
+ },
+ {
+ "text": "'u'",
+ "type": "simple",
+ },
+ {
+ "text": "'v'",
+ "type": "simple",
+ },
+ {
+ "text": "'w'",
+ "type": "simple",
+ },
+ {
+ "text": "'x'",
+ "type": "simple",
+ },
+ {
+ "text": "'y'",
+ "type": "simple",
+ },
+ {
+ "text": "'z'",
+ "type": "simple",
+ },
+ {
+ "text": "'A'",
+ "type": "simple",
+ },
+ {
+ "text": "'B'",
+ "type": "simple",
+ },
+ {
+ "text": "'C'",
+ "type": "simple",
+ },
+ {
+ "text": "'D'",
+ "type": "simple",
+ },
+ {
+ "text": "'E'",
+ "type": "simple",
+ },
+ {
+ "text": "'F'",
+ "type": "simple",
+ },
+ {
+ "text": "'G'",
+ "type": "simple",
+ },
+ {
+ "text": "'H'",
+ "type": "simple",
+ },
+ {
+ "text": "'I'",
+ "type": "simple",
+ },
+ {
+ "text": "'J'",
+ "type": "simple",
+ },
+ {
+ "text": "'K'",
+ "type": "simple",
+ },
+ {
+ "text": "'L'",
+ "type": "simple",
+ },
+ {
+ "text": "'M'",
+ "type": "simple",
+ },
+ {
+ "text": "'N'",
+ "type": "simple",
+ },
+ {
+ "text": "'O'",
+ "type": "simple",
+ },
+ {
+ "text": "'P'",
+ "type": "simple",
+ },
+ {
+ "text": "'Q'",
+ "type": "simple",
+ },
+ {
+ "text": "'R'",
+ "type": "simple",
+ },
+ {
+ "text": "'S'",
+ "type": "simple",
+ },
+ {
+ "text": "'T'",
+ "type": "simple",
+ },
+ {
+ "text": "'U'",
+ "type": "simple",
+ },
+ {
+ "text": "'V'",
+ "type": "simple",
+ },
+ {
+ "text": "'W'",
+ "type": "simple",
+ },
+ {
+ "text": "'X'",
+ "type": "simple",
+ },
+ {
+ "text": "'Y'",
+ "type": "simple",
+ },
+ {
+ "text": "'Z'",
+ "type": "simple",
+ },
+ {
+ "text": "'0'",
+ "type": "simple",
+ },
+ {
+ "text": "'1'",
+ "type": "simple",
+ },
+ {
+ "text": "'2'",
+ "type": "simple",
+ },
+ {
+ "text": "'3'",
+ "type": "simple",
+ },
+ {
+ "text": "'4'",
+ "type": "simple",
+ },
+ {
+ "text": "'5'",
+ "type": "simple",
+ },
+ {
+ "text": "'6'",
+ "type": "simple",
+ },
+ {
+ "text": "'7'",
+ "type": "simple",
+ },
+ {
+ "text": "'8'",
+ "type": "simple",
+ },
+ {
+ "text": "'9'",
+ "type": "simple",
+ },
+ ],
+ },
+ "text": "AlphaNumericChar",
+ "type": "shadow",
+ },
+ ],
+ },
+ },
+ {
+ "default": undefined,
+ "description": "The format parameter.",
+ "name": "options.format?",
+ "type": {
+ "text": "'hex' | 'css' | 'binary' | 'decimal'",
+ "type": "union",
+ "types": [
+ {
+ "text": "'hex'",
+ "type": "simple",
+ },
+ {
+ "text": "'css'",
+ "type": "simple",
+ },
+ {
+ "text": "'binary'",
+ "type": "simple",
+ },
+ {
+ "text": "'decimal'",
+ "type": "simple",
+ },
+ ],
+ },
+ },
+ ],
+ "returns": {
+ "text": "string",
+ "type": "simple",
+ },
+ "seeAlsos": [],
+ "signature": "function stringUnionParamMethod(
+ value: 'a' | 'b',
+ options?: {
+ casing?: Casing;
+ format?: 'hex' | ColorFormat;
+ excludes?: ReadonlyArray<AlphaNumericChar>;
+ }
+ ): string;",
+ "since": "1.0.0",
+ "throws": [],
+ },
+ ],
+ "source": {
+ "column": -1,
+ "filePath": "test/scripts/apidocs/method.example.ts",
+ "line": -1,
+ },
+}
+`;
diff --git a/test/scripts/apidoc/module.example.ts b/test/scripts/apidocs/class.example.ts
index 0e5d9d89..0e5d9d89 100644
--- a/test/scripts/apidoc/module.example.ts
+++ b/test/scripts/apidocs/class.example.ts
diff --git a/test/scripts/apidocs/class.spec.ts b/test/scripts/apidocs/class.spec.ts
new file mode 100644
index 00000000..8ebce847
--- /dev/null
+++ b/test/scripts/apidocs/class.spec.ts
@@ -0,0 +1,22 @@
+import { describe, expect, it } from 'vitest';
+import { processClass } from '../../../scripts/apidocs/processing/class';
+import * as ModuleTests from './class.example';
+import { loadExampleClasses } from './utils';
+
+const modules = loadExampleClasses();
+
+describe('class', () => {
+ it('dummy dependency to rerun the test if the example changes', () => {
+ expect(Object.keys(ModuleTests)).not.toEqual([]);
+ });
+
+ it('expected and actual modules are equal', () => {
+ expect(Object.keys(modules).sort()).toMatchSnapshot();
+ });
+
+ it.each(Object.entries(modules))('processClass(%s)', (_, module) => {
+ const actual = processClass(module);
+
+ expect(actual).toMatchSnapshot();
+ });
+});
diff --git a/test/scripts/apidoc/signature.example.ts b/test/scripts/apidocs/method.example.ts
index c42d2d45..e72f5f3f 100644
--- a/test/scripts/apidoc/signature.example.ts
+++ b/test/scripts/apidocs/method.example.ts
@@ -74,6 +74,8 @@ export type AB = 'a' | 'b';
export class SignatureTest {
/**
* Test with no parameters.
+ *
+ * @since 1.0.0
*/
noParamMethod(): number {
return 0;
@@ -83,6 +85,8 @@ export class SignatureTest {
* Test with a required parameter.
*
* @param a The number parameter.
+ *
+ * @since 1.0.0
*/
requiredNumberParamMethod(a: number): number {
return a;
@@ -92,6 +96,8 @@ export class SignatureTest {
* Test with an optional parameter.
*
* @param b The string parameter.
+ *
+ * @since 1.0.0
*/
optionalStringParamMethod(b?: string): number {
return b ? 0 : 1;
@@ -101,6 +107,8 @@ export class SignatureTest {
* Test with a default parameter.
*
* @param c The boolean parameter.
+ *
+ * @since 1.0.0
*/
defaultBooleanParamMethod(c: boolean = true): number {
return c ? 1 : 0;
@@ -112,6 +120,8 @@ export class SignatureTest {
* @param a The number parameter.
* @param b The string parameter.
* @param c The boolean parameter.
+ *
+ * @since 1.0.0
*/
multiParamMethod(a: number, b?: string, c: boolean = true): number {
return c ? a : b ? 0 : 1;
@@ -121,6 +131,8 @@ export class SignatureTest {
* Test with a function parameters.
*
* @param fn The function parameter.
+ *
+ * @since 1.0.0
*/
functionParamMethod(fn: (a: string) => number): number {
return fn('a');
@@ -134,12 +146,23 @@ export class SignatureTest {
* @param options.casing The casing parameter.
* @param options.format The format parameter.
* @param options.excludes The excludes parameter.
+ *
+ * @since 1.0.0
*/
stringUnionParamMethod(
value: 'a' | 'b',
options?: {
+ /**
+ * The casing parameter.
+ */
casing?: Casing;
+ /**
+ * The format parameter.
+ */
format?: 'hex' | ColorFormat;
+ /**
+ * The excludes parameter.
+ */
excludes?: ReadonlyArray<AlphaNumericChar>;
}
): string {
@@ -155,6 +178,8 @@ export class SignatureTest {
* @param namedArray Array of `'a'` or `'b'`.
* @param mixed Value `'a'` or `'b'` or an array thereof.
* @param namedMixed Value `'a'` or `'b'` or an array thereof.
+ *
+ * @since 1.0.0
*/
literalUnionParamMethod(
value: LiteralUnion<'a' | 'b'>,
@@ -178,6 +203,8 @@ export class SignatureTest {
* Test with a Record parameter.
*
* @param object The Record parameter.
+ *
+ * @since 1.0.0
*/
recordParamMethod(object: Record<string, number>): number {
return object.a;
@@ -192,11 +219,25 @@ export class SignatureTest {
* @param options.c The boolean parameter.
* @param options.d The method parameter.
* @param options.e The LiteralUnion parameter.
+ *
+ * @since 1.0.0
*/
optionsParamMethod(options: {
+ /**
+ * The number parameter.
+ */
a: number;
+ /**
+ * The string parameter.
+ */
b?: string;
+ /**
+ * The boolean parameter.
+ */
c: boolean;
+ /**
+ * The method parameter.
+ */
d: () => string;
/**
* A parameter with inline documentation.
@@ -222,11 +263,28 @@ export class SignatureTest {
* @param b.value The number parameter.
* @param c Parameter with inner jsdocs default.
* @param c.value The number parameter. It also has a more complex description. Defaults to `2`.
+ *
+ * @since 1.0.0
*/
optionsInlineParamMethodWithDefaults(
- a: { value?: number } = { value: 1 },
- b: { value?: number },
- c: { value?: number }
+ a: {
+ /**
+ * The number parameter.
+ */
+ value?: number;
+ } = { value: 1 },
+ b: {
+ /**
+ * The number parameter.
+ */
+ value?: number;
+ },
+ c: {
+ /**
+ * The number parameter.
+ */
+ value?: number;
+ }
): number {
return a.value ?? b.value ?? c.value ?? -1;
}
@@ -240,6 +298,8 @@ export class SignatureTest {
* @param b.value The number parameter.
* @param c Parameter with inner jsdocs default.
* @param c.value The number parameter. Defaults to `2`.
+ *
+ * @since 1.0.0
*/
optionsTypeParamMethodWithDefaults(
a: ParameterOptionsTypeA = { value: 1 },
@@ -258,6 +318,8 @@ export class SignatureTest {
* @param b.value The number parameter.
* @param c Parameter with inner jsdocs default.
* @param c.value The number parameter. Defaults to `2`.
+ *
+ * @since 1.0.0
*/
optionsInterfaceParamMethodWithDefaults(
a: ParameterOptionsInterfaceA = { value: 1 },
@@ -271,7 +333,9 @@ export class SignatureTest {
* Test with example marker.
*
* @example
- * test.apidoc.methodWithExample() // 0
+ * test.apidocs.methodWithExample() // 0
+ *
+ * @since 1.0.0
*/
methodWithExample(): number {
return 0;
@@ -280,7 +344,9 @@ export class SignatureTest {
/**
* Test with deprecated and see marker.
*
- * @see test.apidoc.methodWithExample()
+ * @see test.apidocs.methodWithExample()
+ *
+ * @since 1.0.0
*
* @deprecated do something else
*/
@@ -292,6 +358,8 @@ export class SignatureTest {
* Test with throws.
*
* @throws Everytime.
+ *
+ * @since 1.0.0
*/
methodWithThrows(): number {
throw new FakerError('Test error');
@@ -302,6 +370,8 @@ export class SignatureTest {
*
* @throws First error case.
* @throws Another error case.
+ *
+ * @since 1.0.0
*/
methodWithMultipleThrows(): number {
throw new FakerError('Another test error');
@@ -314,6 +384,8 @@ export class SignatureTest {
* @param option.a Some deprecated option.
* @param option.b Some other deprecated option.
* @param option.c Some other option.
+ *
+ * @since 1.0.0
*/
methodWithDeprecatedOption(option: {
/**
@@ -339,8 +411,10 @@ export class SignatureTest {
/**
* Test with multiple see markers.
*
- * @see test.apidoc.methodWithExample()
- * @see test.apidoc.methodWithDeprecated()
+ * @see test.apidocsmethodWithExample()
+ * @see test.apidocsmethodWithDeprecated()
+ *
+ * @since 1.0.0
*/
methodWithMultipleSeeMarkers(): number {
return 0;
@@ -349,8 +423,10 @@ export class SignatureTest {
/**
* Test with multiple see markers and backticks.
*
- * @see test.apidoc.methodWithExample() with parameter `foo`.
- * @see test.apidoc.methodWithDeprecated() with parameter `bar` and `baz`.
+ * @see test.apidocsmethodWithExample() with parameter `foo`.
+ * @see test.apidocsmethodWithDeprecated() with parameter `bar` and `baz`.
+ *
+ * @since 1.0.0
*/
methodWithMultipleSeeMarkersAndBackticks(): number {
return 0;
@@ -373,6 +449,8 @@ export class SignatureTest {
* @param array Array to pick the value from.
* @param array[].weight The weight of the value.
* @param array[].value The value to pick.
+ *
+ * @since 1.0.0
*/
complexArrayParameter<T>(
array: ReadonlyArray<{
diff --git a/test/scripts/apidocs/method.spec.ts b/test/scripts/apidocs/method.spec.ts
new file mode 100644
index 00000000..14c9cfc0
--- /dev/null
+++ b/test/scripts/apidocs/method.spec.ts
@@ -0,0 +1,30 @@
+import { describe, expect, it } from 'vitest';
+import { processMethodLike } from '../../../scripts/apidocs/processing/method';
+import { SignatureTest } from './method.example';
+import { loadExampleMethods } from './utils';
+
+const methods = loadExampleMethods();
+
+describe('method', () => {
+ it('dummy dependency to rerun the test if the example changes', () => {
+ expect(new SignatureTest()).toBeTruthy();
+ });
+
+ it('expected and actual methods are equal', () => {
+ expect(Object.keys(methods)).toMatchSnapshot();
+ });
+
+ it.each(Object.entries(methods))(
+ 'processMethodLike(%s)',
+ (name, signature) => {
+ const actual = processMethodLike(name, signature);
+ actual.source = {
+ filePath: actual.source.filePath,
+ line: -1,
+ column: -1,
+ };
+
+ expect(actual).toMatchSnapshot();
+ }
+ );
+});
diff --git a/test/scripts/apidocs/utils.ts b/test/scripts/apidocs/utils.ts
new file mode 100644
index 00000000..e15bcd8f
--- /dev/null
+++ b/test/scripts/apidocs/utils.ts
@@ -0,0 +1,38 @@
+import type { ClassDeclaration, MethodDeclaration, SourceFile } from 'ts-morph';
+import { getProject } from '../../../scripts/apidocs/project';
+
+/**
+ * Loads the example methods.
+ */
+export function loadExampleMethods(): Record<string, MethodDeclaration> {
+ return Object.fromEntries(
+ loadProjectFile('test/scripts/apidocs/method.example.ts')
+ .getClassOrThrow('SignatureTest')
+ .getMethods()
+ .map((m) => [m.getName(), m] as const)
+ .sort(([a], [b]) => a.localeCompare(b)) // Relevant for Object.keys() order
+ );
+}
+
+/**
+ * Loads the example classes.
+ */
+export function loadExampleClasses(): Record<string, ClassDeclaration> {
+ return Object.fromEntries(
+ loadProjectFile('test/scripts/apidocs/class.example.ts')
+ .getClasses()
+ .map((m) => [m.getNameOrThrow(), m] as const)
+ .sort(([a], [b]) => a.localeCompare(b)) // Relevant for Object.keys() order
+ );
+}
+
+/**
+ * Loads the project.
+ *
+ * @param sourceFile The source file to load.
+ */
+function loadProjectFile(sourceFile: string): SourceFile {
+ const project = getProject();
+
+ return project.addSourceFileAtPath(sourceFile);
+}
diff --git a/test/scripts/apidocs/verify-jsdoc-tags.spec.ts b/test/scripts/apidocs/verify-jsdoc-tags.spec.ts
new file mode 100644
index 00000000..16f0ea51
--- /dev/null
+++ b/test/scripts/apidocs/verify-jsdoc-tags.spec.ts
@@ -0,0 +1,286 @@
+import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import validator from 'validator';
+import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
+import { processComponents } from '../../../scripts/apidocs/generate';
+import { extractSummaryDefault } from '../../../scripts/apidocs/output/page';
+import { getProject } from '../../../scripts/apidocs/project';
+
+// This test suite ensures, that every method
+// - has working examples
+// - running these do not log anything, unless the method is deprecated
+// - has a valid @since tag
+// - has valid @see tags
+// - has proper links in the description
+
+const tempDir = resolve(dirname(fileURLToPath(import.meta.url)), 'temp');
+const relativeImportPath = `${'../'.repeat(5)}src`;
+
+afterAll(() => {
+ // Remove temp folder
+ if (existsSync(tempDir)) {
+ rmSync(tempDir, { recursive: true });
+ }
+});
+
+const modules = processComponents(getProject());
+
+function resolveDirToModule(moduleName: string): string {
+ return resolve(tempDir, moduleName);
+}
+
+function resolvePathToMethodFile(
+ moduleName: string,
+ methodName: string,
+ signature: number
+): string {
+ const dir = resolveDirToModule(moduleName);
+ return resolve(dir, `${methodName}_${signature}.ts`);
+}
+
+const allowedReferences = new Set(
+ modules.flatMap(({ camelTitle, methods, category }) => {
+ return methods.map(({ name }) =>
+ category ? `faker.${camelTitle}.${name}` : `${camelTitle}.${name}`
+ );
+ })
+);
+const allowedLinks = new Set(
+ modules.flatMap(({ camelTitle, methods }) => {
+ return [
+ `/api/${camelTitle}.html`,
+ ...methods.map(
+ ({ name }) => `/api/${camelTitle}.html#${name.toLowerCase()}`
+ ),
+ ];
+ })
+);
+
+function assertDescription(description: string): void {
+ const linkRegexp = /\[([^\]]+)\]\(([^)]+)\)/g;
+ const links = [...description.matchAll(linkRegexp)].map((m) => m[2]);
+
+ for (const link of links) {
+ expect(link).toMatch(/^https?:\/\//);
+ expect(link).toSatisfy(validator.isURL);
+
+ if (link.includes('fakerjs.dev/api/')) {
+ expect(allowedLinks, `${link} to point to a valid target`).toContain(
+ link.replace(/.*fakerjs.dev\//, '/')
+ );
+ }
+ }
+}
+
+describe('verify JSDoc tags', () => {
+ describe.each(modules.map((m) => [m.camelTitle, m]))(
+ '%s',
+ (moduleName, module) => {
+ describe('verify module', () => {
+ it('verify description', () => {
+ assertDescription(module.description);
+ });
+ });
+
+ describe.each(module.methods.map((m) => [m.name, m]))(
+ '%s',
+ (methodName, method) => {
+ describe.each(method.signatures.map((s, i) => [i, s]))(
+ '%i',
+ (signatureIndex, signature) => {
+ beforeAll(() => {
+ // Write temp files to disk
+ // By extracting the examples
+ // Guessing required imports
+ // And saving them to disk for later execution
+
+ const dir = resolveDirToModule(moduleName);
+ mkdirSync(dir, { recursive: true });
+ const path = resolvePathToMethodFile(
+ moduleName,
+ methodName,
+ signatureIndex
+ );
+
+ let examples = signature.examples.join('\n');
+ if (moduleName === 'faker' && methodName === 'constructor') {
+ // That case should demonstrate an error and is thus not suitable for testing
+ examples = examples.replace(
+ 'customFaker.music.genre()',
+ '// customFaker.music.genre()'
+ );
+ }
+
+ // Replace imports for users with our source path
+ examples = examples.replaceAll(
+ " from '@faker-js/faker'",
+ ` from '${relativeImportPath}'`
+ );
+
+ if (moduleName === 'randomizer') {
+ examples = `import { generateMersenne32Randomizer } from '${relativeImportPath}/internal/mersenne';
+
+const randomizer = generateMersenne32Randomizer();
+
+${examples}`;
+ }
+
+ // If imports are present, we expect them to be complete
+ if (!examples.includes('import ')) {
+ const imports = [
+ // collect the imports for the various locales e.g. fakerDE_CH
+ ...new Set(examples.match(/(?<!\.)faker[^.]*(?=\.)/g)),
+ ];
+
+ if (imports.length > 0) {
+ examples = `import { ${imports.join(
+ ', '
+ )} } from '${relativeImportPath}';\n\n${examples}`;
+ }
+ }
+
+ writeFileSync(path, examples);
+ });
+
+ it('verify description', () => {
+ assertDescription(signature.description);
+ });
+
+ it('verify @example tag', async () => {
+ const examples = signature.examples.join('\n');
+
+ expect(
+ examples,
+ `${moduleName}.${methodName} to have examples`
+ ).not.toBe('');
+
+ // Grab path to example file
+ const path = resolvePathToMethodFile(
+ moduleName,
+ methodName,
+ signatureIndex
+ );
+
+ // Executing the examples should not throw
+ await expect(
+ import(`${path}?scope=example`),
+ examples
+ ).resolves.toBeDefined();
+ });
+
+ // This only checks whether the whole method is deprecated or not
+ // It does not check whether the method is deprecated for a specific set of arguments
+ it('verify @deprecated tag', async () => {
+ // Grab path to example file
+ const path = resolvePathToMethodFile(
+ moduleName,
+ methodName,
+ signatureIndex
+ );
+
+ const consoleWarnSpy = vi.spyOn(console, 'warn');
+
+ // Run the examples
+ await import(`${path}?scope=deprecated`);
+
+ // Verify that deprecated methods log a warning
+ const { deprecated } = signature;
+ if (deprecated == null) {
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
+ } else {
+ expect(consoleWarnSpy).toHaveBeenCalled();
+ expect(deprecated).not.toBe('');
+ }
+ });
+
+ describe.each(signature.parameters.map((p) => [p.name, p]))(
+ '%s',
+ (_, parameter) => {
+ it('verify default value', () => {
+ const {
+ name,
+ default: paramDefault,
+ description,
+ } = parameter;
+
+ const commentDefault = extractSummaryDefault(description);
+ if (paramDefault) {
+ if (
+ /^{.*}$/.test(paramDefault) ||
+ paramDefault.includes('\n')
+ ) {
+ expect(commentDefault).toBeUndefined();
+ } else if (
+ !name.includes('.') &&
+ // Skip check of defaults in descriptions if it is a paraphrased function call
+ (commentDefault ||
+ (!description.includes('Defaults to') &&
+ !paramDefault.includes('(')))
+ ) {
+ expect(
+ commentDefault,
+ `Expect '${name}'s js implementation default to be the same as the jsdoc summary default`
+ ).toBe(paramDefault);
+ }
+ }
+ });
+
+ it('verify description', () => {
+ assertDescription(parameter.description);
+ });
+ }
+ );
+
+ it('verify @see tags', () => {
+ for (const link of signature.seeAlsos) {
+ if (link.startsWith('faker.')) {
+ // Expected @see faker.xxx.yyy()
+ expect(
+ link,
+ 'Expect method reference to contain ()'
+ ).toContain('(');
+ expect(
+ link,
+ 'Expect method reference to contain ()'
+ ).toContain(')');
+ expect(
+ link,
+ "Expect method reference to have a ': ' after the parenthesis"
+ ).toContain('): ');
+ expect(
+ link,
+ 'Expect method reference to have a description starting with a capital letter'
+ ).toMatch(/\): [A-Z]/);
+ expect(
+ link,
+ 'Expect method reference to start with a standard description phrase'
+ ).toMatch(
+ /\): (?:For generating |For more information about |For using |For the replacement method)/
+ );
+ expect(
+ link,
+ 'Expect method reference to have a description ending with a dot'
+ ).toMatch(/\.$/);
+ expect(allowedReferences).toContain(
+ link.replace(/\(.*/, '')
+ );
+ }
+ }
+ });
+
+ it('verify @since tag', () => {
+ const { since } = signature;
+ expect(since, '@since to be present').toBeTruthy();
+ expect(since).not.toBe('');
+ expect(since, '@since to be a valid semver').toSatisfy(
+ validator.isSemVer
+ );
+ });
+ }
+ );
+ }
+ );
+ }
+ );
+});
diff --git a/tsconfig.json b/tsconfig.json
index a290fc07..a949c9c3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,6 +17,6 @@
"node_modules",
"dist",
// required for the typedoc related tests on macOS #2280
- "test/scripts/apidoc/temp"
+ "test/scripts/apidocs/temp"
]
}