From c115056e04d1e42f97c8d77daed3d9056c375953 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Mon, 7 Mar 2022 20:37:13 +0100 Subject: [PATCH 1/2] docs: display correct signature (#596) --- scripts/apidoc/directMethods.ts | 5 +++-- scripts/apidoc/moduleMethods.ts | 3 ++- src/random.ts | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/scripts/apidoc/directMethods.ts b/scripts/apidoc/directMethods.ts index 616226ee522..159ad84833d 100644 --- a/scripts/apidoc/directMethods.ts +++ b/scripts/apidoc/directMethods.ts @@ -41,8 +41,9 @@ export function processDirectMethod( methodName.substring(0, 1).toUpperCase() + methodName.substring(1); console.log(`Processing Direct: ${upperMethodName}`); - const signature = (direct.type as TypeDoc.ReflectionType).declaration - .signatures[0]; + const signatures = (direct.type as TypeDoc.ReflectionType).declaration + .signatures; + const signature = signatures[signatures.length - 1]; writeApiDocsDirectPage(methodName); writeApiDocsData(methodName, [ diff --git a/scripts/apidoc/moduleMethods.ts b/scripts/apidoc/moduleMethods.ts index 89c42a33a56..7b090d13e9c 100644 --- a/scripts/apidoc/moduleMethods.ts +++ b/scripts/apidoc/moduleMethods.ts @@ -50,7 +50,8 @@ function processModuleMethod(module: TypeDoc.DeclarationReflection): PageIndex { )) { const methodName = method.name; console.debug(`- ${methodName}`); - const signature = method.signatures[0]; + const signatures = method.signatures; + const signature = signatures[signatures.length - 1]; methods.push(analyzeSignature(signature, lowerModuleName, methodName)); } diff --git a/src/random.ts b/src/random.ts index 47720de2b6b..c2d8ecb2bb4 100644 --- a/src/random.ts +++ b/src/random.ts @@ -179,6 +179,26 @@ export class Random { object: T, field?: unknown ): T[K]; + /** + * Returns a random key or value from given object. + * + * @template T The type of `Record` to pick from. + * @template K The keys of `T`. + * @param object The object to get the keys or values from. + * @param field If this is set to `'key'`, this method will a return a random key of the given instance. + * If this is set to `'value'`, this method will a return a random value of the given instance. + * Defaults to `'value'`. + * + * @example + * const object = { keyA: 'valueA', keyB: 42 }; + * faker.random.objectElement(object) // 42 + * faker.random.objectElement(object, 'key') // 'keyB' + * faker.random.objectElement(object, 'value') // 'valueA' + */ + objectElement, K extends keyof T>( + object: T, + field?: 'key' | 'value' + ): K | T[K]; objectElement, K extends keyof T>( object = { foo: 'bar', too: 'car' } as unknown as T, field = 'value' From c99160f0ab059729af29d7ee08bd97c38d323b2a Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Mon, 7 Mar 2022 20:47:22 +0100 Subject: [PATCH 2/2] docs: add test for api docs generation (#574) --- test/scripts/apidoc/.gitignore | 1 + test/scripts/apidoc/signature.example.ts | 88 +++++++++++ test/scripts/apidoc/signature.expected.json | 155 ++++++++++++++++++++ test/scripts/apidoc/signature.spec.ts | 53 +++++++ test/scripts/apidoc/tsconfig.json | 3 + 5 files changed, 300 insertions(+) create mode 100644 test/scripts/apidoc/.gitignore create mode 100644 test/scripts/apidoc/signature.example.ts create mode 100644 test/scripts/apidoc/signature.expected.json create mode 100644 test/scripts/apidoc/signature.spec.ts create mode 100644 test/scripts/apidoc/tsconfig.json diff --git a/test/scripts/apidoc/.gitignore b/test/scripts/apidoc/.gitignore new file mode 100644 index 00000000000..b0645e5bd67 --- /dev/null +++ b/test/scripts/apidoc/.gitignore @@ -0,0 +1 @@ +*.actuals.json diff --git a/test/scripts/apidoc/signature.example.ts b/test/scripts/apidoc/signature.example.ts new file mode 100644 index 00000000000..8f1ada32f4d --- /dev/null +++ b/test/scripts/apidoc/signature.example.ts @@ -0,0 +1,88 @@ +export class SignatureTest { + /** + * Test with no parameters. + */ + noParamMethod(): number { + return 0; + } + + /** + * Test with a required parameter. + * + * @param a The number parameter. + */ + requiredNumberParamMethod(a: number): number { + return a; + } + + /** + * Test with an optional parameter. + * + * @param b The string parameter. + */ + optionalStringParamMethod(b?: string): number { + return +b; + } + + /** + * Test with a default parameter. + * + * @param c The boolean parameter. + */ + defaultBooleanParamMethod(c: boolean = true): number { + return c ? 1 : 0; + } + + /** + * Test with multiple parameters. + * + * @param a The number parameter. + * @param b The string parameter. + * @param c The boolean parameter. + */ + multiParamMethod(a: number, b?: string, c: boolean = true): number { + return c ? a : +b; + } + + /** + * Test with a function parameters. + * + * @param fn The function parameter. + */ + functionParamMethod(fn: (a: string) => number): number { + return fn('a'); + } + + /** + * Test with a function parameters. + * + * @param options The function parameter. + * @param options.a The number parameter. + * @param options.b The string parameter. + * @param options.c The boolean parameter. + */ + optionsParamMethod(options: { a: number; b?: string; c: boolean }): number { + return options.c ? options.a : +options.b; + } + + /** + * Test with example marker. + * + * @example + * test.apidoc.methodWithExample() // 0 + */ + methodWithExample(): number { + return 0; + } + + /** + * Test with deprecated and see marker. + * + * @see test.apidoc.methodWithExample() + * + * @deprecated + */ + methodWithDeprecated(): number { + return 0; + } +} diff --git a/test/scripts/apidoc/signature.expected.json b/test/scripts/apidoc/signature.expected.json new file mode 100644 index 00000000000..d0674899053 --- /dev/null +++ b/test/scripts/apidoc/signature.expected.json @@ -0,0 +1,155 @@ +{ + "noParamMethod": { + "name": "noParamMethod", + "title": "No Param Method", + "description": "

Test with no parameters.

\n", + "parameters": [], + "returns": "number", + "examples": "
faker.noParamMethod(): number\n
\n
", + "deprecated": false, + "seeAlsos": [] + }, + "defaultBooleanParamMethod": { + "name": "defaultBooleanParamMethod", + "title": "Default Boolean Param Method", + "description": "

Test with a default parameter.

\n", + "parameters": [ + { + "name": "c", + "type": "boolean", + "default": "true", + "description": "

The boolean parameter.

\n" + } + ], + "returns": "number", + "examples": "
faker.defaultBooleanParamMethod(c: boolean = true): number\n
\n
", + "deprecated": false, + "seeAlsos": [] + }, + "functionParamMethod": { + "name": "functionParamMethod", + "title": "Function Param Method", + "description": "

Test with a function parameters.

\n", + "parameters": [ + { + "name": "fn", + "type": "Function", + "description": "

The function parameter.

\n" + } + ], + "returns": "number", + "examples": "
faker.functionParamMethod(fn: Function): number\n
\n
", + "deprecated": false, + "seeAlsos": [] + }, + "methodWithDeprecated": { + "name": "methodWithDeprecated", + "title": "Method With Deprecated", + "description": "

Test with deprecated and see marker.

\n", + "parameters": [], + "returns": "number", + "examples": "
faker.methodWithDeprecated(): number\n
\n
", + "deprecated": true, + "seeAlsos": ["test.apidoc.methodWithExample()"] + }, + "methodWithExample": { + "name": "methodWithExample", + "title": "Method With Example", + "description": "

Test with example marker.

\n", + "parameters": [], + "returns": "number", + "examples": "
faker.methodWithExample(): number\ntest.apidoc.methodWithExample() // 0\n
\n
", + "deprecated": false, + "seeAlsos": [] + }, + "multiParamMethod": { + "name": "multiParamMethod", + "title": "Multi Param Method", + "description": "

Test with multiple parameters.

\n", + "parameters": [ + { + "name": "a", + "type": "number", + "description": "

The number parameter.

\n" + }, + { + "name": "b?", + "type": "string", + "description": "

The string parameter.

\n" + }, + { + "name": "c", + "type": "boolean", + "default": "true", + "description": "

The boolean parameter.

\n" + } + ], + "returns": "number", + "examples": "
faker.multiParamMethod(a: number, b?: string, c: boolean = true): number\n
\n
", + "deprecated": false, + "seeAlsos": [] + }, + "optionalStringParamMethod": { + "name": "optionalStringParamMethod", + "title": "Optional String Param Method", + "description": "

Test with an optional parameter.

\n", + "parameters": [ + { + "name": "b?", + "type": "string", + "description": "

The string parameter.

\n" + } + ], + "returns": "number", + "examples": "
faker.optionalStringParamMethod(b?: string): number\n
\n
", + "deprecated": false, + "seeAlsos": [] + }, + "optionsParamMethod": { + "name": "optionsParamMethod", + "title": "Options Param Method", + "description": "

Test with a function parameters.

\n", + "parameters": [ + { + "name": "options", + "type": "Object", + "description": "

The function parameter.

\n" + }, + { + "name": "options.a", + "type": "number", + "description": "

The number parameter.

\n" + }, + { + "name": "options.b?", + "type": "string", + "description": "

The string parameter.

\n" + }, + { + "name": "options.c", + "type": "boolean", + "description": "

The boolean parameter.

\n" + } + ], + "returns": "number", + "examples": "
faker.optionsParamMethod(options: Object): number\n
\n
", + "deprecated": false, + "seeAlsos": [] + }, + "requiredNumberParamMethod": { + "name": "requiredNumberParamMethod", + "title": "Required Number Param Method", + "description": "

Test with a required parameter.

\n", + "parameters": [ + { + "name": "a", + "type": "number", + "description": "

The number parameter.

\n" + } + ], + "returns": "number", + "examples": "
faker.requiredNumberParamMethod(a: number): number\n
\n
", + "deprecated": false, + "seeAlsos": [] + } +} diff --git a/test/scripts/apidoc/signature.spec.ts b/test/scripts/apidoc/signature.spec.ts new file mode 100644 index 00000000000..f536a219e47 --- /dev/null +++ b/test/scripts/apidoc/signature.spec.ts @@ -0,0 +1,53 @@ +import { writeFileSync } from 'fs'; +import { resolve } from 'path'; +import * as TypeDoc from 'typedoc'; +import { afterAll, describe, expect, it } from 'vitest'; +import type { Method } from '../../../docs/.vitepress/components/api-docs/method'; +import { analyzeSignature } from '../../../scripts/apidoc/signature'; +import expected_ from './signature.expected.json'; +const expected: Record = expected_; + +function prettyJson(object): string { + return JSON.stringify(object, null, 2); +} + +describe('signature', () => { + const app = new TypeDoc.Application(); + + app.options.addReader(new TypeDoc.TSConfigReader()); + + app.bootstrap({ + entryPoints: ['test/scripts/apidoc/signature.example.ts'], + tsconfig: 'test/scripts/apidoc/tsconfig.json', + }); + + const methods: Record = app + .convert() + .getChildrenByKind(TypeDoc.ReflectionKind.Class)[0] + .getChildrenByKind(TypeDoc.ReflectionKind.Method) + .reduce((a, v) => ({ ...a, [v.name]: v }), {}); + + describe('analyzeSignature()', () => { + const actuals = {}; + + it('expected and actual methods are equal', () => { + expect(Object.keys(methods).sort()).toEqual(Object.keys(expected).sort()); + }); + + it.each(Object.keys(expected))('%s', (name) => { + const method = methods[name]; + const actual = analyzeSignature(method.signatures[0], null, method.name); + actuals[name] = actual; + + expect(prettyJson(actual)).toBe(prettyJson(expected[name])); + }); + + afterAll(() => { + // Write to file for easier comparison + writeFileSync( + resolve('test', 'scripts', 'apidoc', 'signature.actuals.json'), + prettyJson(actuals) + ); + }); + }); +}); diff --git a/test/scripts/apidoc/tsconfig.json b/test/scripts/apidoc/tsconfig.json new file mode 100644 index 00000000000..ebc4e81722b --- /dev/null +++ b/test/scripts/apidoc/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["signature.example.ts"] +}