diff --git a/internals/cli/package.json b/internals/cli/package.json index bce84490b..7b7ae7a87 100644 --- a/internals/cli/package.json +++ b/internals/cli/package.json @@ -24,7 +24,7 @@ "inquirer": "^9.2.10", "ts-node": "^10.9.1", "tsc-alias": "^1.8.7", - "typedoc": "^0.25.1" + "typedoc": "^0.24.0" }, "devDependencies": { "@commander-js/extra-typings": "^11.0.0", diff --git a/internals/cli/src/commands/write/docs/api.ts b/internals/cli/src/commands/write/docs/api.ts index b699cec83..8d5fcac45 100644 --- a/internals/cli/src/commands/write/docs/api.ts +++ b/internals/cli/src/commands/write/docs/api.ts @@ -5,7 +5,8 @@ import { mkdirp } from '@internals/common/fs'; import { writeAPIDocs } from '../../../lib/commands/write/docs/api/index.js'; import { clearOutMarkdownFiles } from '../../../lib/utils/clear-out-markdown-files.js'; import { startWatch } from '../../../lib/cli/start-watch.js'; -import { info } from '@internals/common/logger'; +import { info, verbose } from '@internals/common/logger'; +import { exec, spawn } from 'child_process'; const EXAMPLES_DOCS_DEST = path.resolve(DOCS_DIR, 'docs/documentation/api'); @@ -15,8 +16,10 @@ interface Opts { } const writeAPIDocumentation = async ({ shouldClearMarkdown }: Pick) => { + info('Writing API documentation'); await mkdirp(EXAMPLES_DOCS_DEST); if (shouldClearMarkdown) { + verbose(`Clearing out markdown files in ${EXAMPLES_DOCS_DEST}`) await clearOutMarkdownFiles(EXAMPLES_DOCS_DEST); } @@ -25,20 +28,18 @@ const writeAPIDocumentation = async ({ shouldClearMarkdown }: Pick program.command('api') .description('Write API documentation') - .action(async ({ watch, ...opts }: Opts) => { + .action(async ({ watch, shouldClearMarkdown }: Opts) => { if (watch) { - return startWatch(() => { - info('Writing API documentation'); - return writeAPIDocumentation(opts); - }, [ - path.join(CORE_DIR, `**/*`), - path.join(UPSCALER_DIR, '**/*'), - path.join(CLI_DIR, 'src/lib/write/docs/api/**/*'), - ], { + return startWatch( + `pnpm cli write docs api ${shouldClearMarkdown ? '-c' : ''}`, + [ + path.join(CORE_DIR, `**/*`), + path.join(UPSCALER_DIR, '**/*'), + ], { ignored: path.join(UPSCALER_DIR, '**/*.generated.ts'), persistent: true, }); } else { - writeAPIDocumentation(opts); + writeAPIDocumentation({ shouldClearMarkdown }); } }); diff --git a/internals/cli/src/lib/cli/build-commands-tree.test.ts b/internals/cli/src/lib/cli/build-commands-tree.test.ts index 1c7d3d7cf..128909723 100644 --- a/internals/cli/src/lib/cli/build-commands-tree.test.ts +++ b/internals/cli/src/lib/cli/build-commands-tree.test.ts @@ -18,10 +18,10 @@ vi.mock('fs-extra', () => { } }); -vi.mock('foo/index.js', () => ({ default: vi.fn(), })); -vi.mock('foo/guide/index.js', () => ({ default: vi.fn(), })); -vi.mock('foo/guide/file1.ts', () => ({ default: vi.fn(), })); -vi.mock('foo/model/index.js', () => ({ default: vi.fn(), })); +vi.mock('foo/index.js', () => ({ postProcess: vi.fn(), default: vi.fn(), })); +vi.mock('foo/guide/index.js', () => ({ postProcess: vi.fn(), default: vi.fn(), })); +vi.mock('foo/guide/file1.ts', () => ({ postProcess: vi.fn(), default: vi.fn(), })); +vi.mock('foo/model/index.js', () => ({ postProcess: vi.fn(), default: vi.fn(), })); describe('buildCommandsTree', () => { it('returns a single node for an empty directory, where that node is the directory', async () => { diff --git a/internals/cli/src/lib/cli/start-watch.ts b/internals/cli/src/lib/cli/start-watch.ts index 9989254cc..bcc8fc837 100644 --- a/internals/cli/src/lib/cli/start-watch.ts +++ b/internals/cli/src/lib/cli/start-watch.ts @@ -1,20 +1,91 @@ import chokidar from 'chokidar'; -import { error } from '@internals/common/logger'; +import { error, verbose } from '@internals/common/logger'; +import { ChildProcess, spawn } from 'child_process'; +import path from 'path'; +import { CLI_DIR, ROOT_DIR } from '@internals/common/constants'; -const TIMEOUT_LENGTH = 100; +const TIMEOUT_LENGTH = 250; -export const startWatch = (fn: () => Promise, ...args: Parameters) => { - const watcher = chokidar.watch(...args); +class SpawnError extends Error { + code: null | number; + constructor(code: null | number) { + super(`Exited with code ${code}`); + this.code = code; + } +} + +export const spawnProcessThatInheritsStdioAndPreservesColor = (_cmd: string): [ChildProcess, Promise] => { + let resolve: () => void; + let reject: (err: unknown) => void; + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + const cmd = _cmd.split(' '); + const spawnedProcess = spawn(cmd[0], cmd.slice(1), { + shell: true, + stdio: 'inherit', + }); + spawnedProcess.on('close', (code) => { + if (code !== 0) { + reject(new SpawnError(code)); + } else { + resolve(); + } + }); + process.on('exit', () => { + console.log('exiting, kill process', spawnedProcess.pid); + spawnedProcess.kill(); + }); + return [ + spawnedProcess, + promise, + ]; +}; + +const d = (now: number) => ((performance.now() - now) / 1000).toFixed(2); + +export const startWatch = ( + cmd: string, + paths: Parameters[0], + options: Parameters[1], +) => { + const watcher = chokidar.watch([ + ...paths, + path.join(CLI_DIR, 'src/**/*'), + ], options); let timer: NodeJS.Timeout; - watcher.on('all', async () => { + let last: number; + let iterations = 0; + let spawnedProcess: ChildProcess | undefined; + watcher.on('all', async (event, file, stats) => { + console.clear(); + if (spawnedProcess) { + verbose(`>> Killing previous spawned process ${spawnedProcess.pid}`); + spawnedProcess.kill(); + } clearTimeout(timer); timer = setTimeout(async () => { try { - await fn(); - } catch (err) { - error(err); + verbose(`Running command "${cmd.trim()}" in watch mode, iteration ${iterations++} at ${new Date().toLocaleTimeString()}, ${last ? `last run was ${d(last)}s ago` : 'no last run'}`); + verbose(`>> Running because of event "${event}" on file "${file.split(ROOT_DIR).pop()}"`); + const now = performance.now(); + const [child, promise] = spawnProcessThatInheritsStdioAndPreservesColor(cmd); + spawnedProcess = child; + await promise.catch(err => { + if (err instanceof SpawnError) { + // if (err.code !== null) { + // error(err.message); + // } + } else { + error(err.message); + } + }); + verbose(`Ran command "${cmd.trim()}" in watch mode, took ${d(now)}s`); + spawnedProcess = undefined; + } finally { + last = performance.now(); } }, TIMEOUT_LENGTH); }); }; - diff --git a/internals/cli/src/lib/commands/write/docs/api/_templates/source.md.t b/internals/cli/src/lib/commands/write/docs/api/_templates/source.md.t index 9a716987c..aace97a79 100644 --- a/internals/cli/src/lib/commands/write/docs/api/_templates/source.md.t +++ b/internals/cli/src/lib/commands/write/docs/api/_templates/source.md.t @@ -1 +1 @@ -Defined in <%- prettyFileName ->:<%- line %> +Defined in <%- prettyFileName %>:<%- line %> diff --git a/internals/cli/src/lib/commands/write/docs/api/constants.ts b/internals/cli/src/lib/commands/write/docs/api/constants.ts index 3fa6632f1..7c04e8d91 100644 --- a/internals/cli/src/lib/commands/write/docs/api/constants.ts +++ b/internals/cli/src/lib/commands/write/docs/api/constants.ts @@ -33,7 +33,7 @@ export const TYPES_TO_EXPAND: Record = { }; export const TEMPLATES_DIR = path.resolve(__dirname, '_templates'); -const makeNewExternalType = (name: string, _url: string): DeclarationReflection => { +export const makeNewExternalType = (name: string, _url: string): DeclarationReflection => { const type = new DeclarationReflection(name, ReflectionKind['SomeType']); type.sources = []; return type; diff --git a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-all-declaration-reflections.ts b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-all-declaration-reflections.ts index d017fda2b..d8965a3d1 100644 --- a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-all-declaration-reflections.ts +++ b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-all-declaration-reflections.ts @@ -2,7 +2,8 @@ import { CORE_DIR, UPSCALER_DIR } from "@internals/common/constants"; import { CORE_TSCONFIG_PATH, UPSCALER_TSCONFIG_PATH } from "../constants.js"; import { DeclarationReflection } from "typedoc"; import { getDeclarationReflectionsFromPackages } from "./get-declaration-reflections-from-packages.js"; -import { PlatformSpecificFileDeclarationReflection, getTypesFromPlatformSpecificUpscalerFiles } from "./get-types-from-platform-specific-upscaler-files.js"; +import { getTypesFromPlatformSpecificUpscalerFiles } from "./get-types-from-platform-specific-upscaler-files.js"; +import { PlatformSpecificFileDeclarationReflection } from "../types.js"; const DECLARATION_REFLECTION_FILE_DEFINITIONS = [{ tsconfigPath: UPSCALER_TSCONFIG_PATH, diff --git a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-declaration-reflections-from-packages.ts b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-declaration-reflections-from-packages.ts index 3de386840..6322c23b3 100644 --- a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-declaration-reflections-from-packages.ts +++ b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-declaration-reflections-from-packages.ts @@ -1,5 +1,3 @@ -import { CORE_DIR, UPSCALER_DIR } from "@internals/common/constants"; -import { CORE_SRC_PATH, CORE_TSCONFIG_PATH, UPSCALER_SRC_PATH, UPSCALER_TSCONFIG_PATH } from "../constants.js"; import { getPackageAsTree } from "./get-package-as-tree.js"; import { DeclarationReflection } from "typedoc"; import path from "path"; @@ -9,24 +7,16 @@ export interface ProjectDefinition { projectRoot: string; } - // { - // tsconfigPath: UPSCALER_TSCONFIG_PATH, - // projectRoot: UPSCALER_DIR, - // }, - // { - // tsconfigPath: CORE_TSCONFIG_PATH, - // projectRoot: CORE_DIR, - // }, export const getDeclarationReflectionsFromPackages = (projectDefinitions: ProjectDefinition[]): DeclarationReflection[] => [ ...projectDefinitions, ].reduce((arr, { tsconfigPath, projectRoot }) => { - const tree = getPackageAsTree( + const { children } = getPackageAsTree( path.join(projectRoot, 'src'), tsconfigPath, projectRoot, ); - if (!tree.children?.length) { + if (children === undefined || children.length === 0) { throw new Error(`No children were found for ${projectRoot}. Indicates an error in the returned structure from getPackageAsTree`); } - return arr.concat(tree.children); + return arr.concat(children); }, []); diff --git a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-definitions.ts b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-definitions.ts index 87afa298b..550d1f7f3 100644 --- a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-definitions.ts +++ b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-definitions.ts @@ -1,9 +1,9 @@ import { ReflectionKind } from "typedoc"; -import { DecRef, Definitions } from "../types.js"; +import { Definitions, isPlatformSpecificFileDeclarationReflection } from "../types.js"; import { getAllDeclarationReflections } from "./get-all-declaration-reflections.js"; -import { isPlatformSpecificFileDeclarationReflection } from "./get-types-from-platform-specific-upscaler-files.js"; +import { } from "./get-types-from-platform-specific-upscaler-files.js"; -const KindStringKey: Partial> = { +export const KindStringKey: Partial> = { [ReflectionKind.Constructor]: 'constructors', [ReflectionKind.Method]: 'methods', [ReflectionKind.Interface]: 'interfaces', diff --git a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.test.ts b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.test.ts index 0262df225..16f672a9a 100644 --- a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.test.ts +++ b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.test.ts @@ -18,10 +18,14 @@ describe('makeDeclarationReflection', () => { vi.resetAllMocks(); }); it('gets a declaration reflection', () => { - const decRef = makeDeclarationReflection('foo', ReflectionKind.Function, 'bar' as unknown as SomeType); + const decRef = makeDeclarationReflection('foo', { + type: 'functions', + } as unknown as SomeType); expect(decRef.name).toEqual('foo'); expect(decRef.kind).toEqual(ReflectionKind.Function); - expect(decRef.type).toEqual('bar'); + expect(decRef.type).toEqual({ + type: 'functions', + }); }) }); @@ -70,6 +74,9 @@ describe('getTypesFromPlatformSpecificUpscalerFile', () => { const typeName = 'typeName'; const child = { name: typeName, + type: { + type: 'functions', + }, }; vi.mocked(getPackageAsTree).mockImplementation(() => { return { @@ -84,26 +91,6 @@ describe('getTypesFromPlatformSpecificUpscalerFile', () => { expect(result.browser).toEqual(child); expect(result.node).toEqual(child); }); - - it('throws if there is a mismatch between platforms', () => { - const typeName = 'typeName'; - vi.mocked(getPackageAsTree).mockImplementation((srcPath: string) => { - const child = srcPath.includes('browser') ? { - name: typeName, - type: 'browser', - } : { - name: typeName, - type: 'node', - }; - return { - children: [child], - } as unknown as ProjectReflection; - }); - expect(() => getTypesFromPlatformSpecificUpscalerFile({ - fileName: 'fileName', - typeName, - })).toThrow(); - }); }); describe('getTypesFromPlatformSpecificFiles', () => { @@ -114,7 +101,9 @@ describe('getTypesFromPlatformSpecificFiles', () => { const typeName = 'typeName'; const child = { name: typeName, - type: 'sometype', + type: { + type: 'functions', + }, }; vi.mocked(getPackageAsTree).mockImplementation(() => { return { diff --git a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.ts b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.ts index 80ad7d1db..152f03b8f 100644 --- a/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.ts +++ b/internals/cli/src/lib/commands/write/docs/api/get-definitions/get-types-from-platform-specific-upscaler-files.ts @@ -1,15 +1,9 @@ import { TFJSLibrary } from "@internals/common/tfjs-library"; import { DeclarationReflection, ReflectionKind, SomeType } from "typedoc"; -import { scaffoldUpscaler } from "../../../../../../commands/scaffold/upscaler.js"; import { getPackageAsTree } from "./get-package-as-tree.js"; import { UPSCALER_DIR } from "@internals/common/constants"; import path from "path"; - -export interface PlatformSpecificFileDeclarationReflection { - declarationReflection: DeclarationReflection; - browser: DeclarationReflection; - node: DeclarationReflection; -} +import { Definitions, PlatformSpecificFileDeclarationReflection } from "../types.js"; export interface PlatformSpecificFileDefinition { fileName: string; @@ -18,11 +12,32 @@ export interface PlatformSpecificFileDefinition { const tfjsLibraries: TFJSLibrary[] = ['browser', 'node']; -export const isPlatformSpecificFileDeclarationReflection = ( - child: DeclarationReflection | PlatformSpecificFileDeclarationReflection -): child is PlatformSpecificFileDeclarationReflection => 'browser' in child; +const reverseKindStringKey: Record = { + constructors: ReflectionKind.Constructor, + methods: ReflectionKind.Method, + interfaces: ReflectionKind.Interface, + types: ReflectionKind.TypeAlias, + classes: ReflectionKind.Class, + functions: ReflectionKind.Function, + enums: ReflectionKind.Enum, +}; -export const makeDeclarationReflection = (typeName: string, kind: ReflectionKind, type?: SomeType) => { +export const makeDeclarationReflection = (typeName: string, type: SomeType): DeclarationReflection => { + if (type.type === 'union') { + // const childType = type.types?.[0]; + // console.log(typeName, type.types); + // if (!childType) { + // throw new Error('No child type for union'); + // } + // return makeDeclarationReflection(typeName, childType); + const declarationReflection = new DeclarationReflection(typeName, ReflectionKind.Interface); + declarationReflection.type = type; + return declarationReflection; + } + const kind = reverseKindStringKey[type.type]; + if (kind === undefined) { + throw new Error(`Kind is undefined for type ${type.type}`); + } const declarationReflection = new DeclarationReflection(typeName, kind); declarationReflection.type = type; return declarationReflection; @@ -49,12 +64,26 @@ export const getPlatformSpecificUpscalerDeclarationReflections = ( export const getTypesFromPlatformSpecificUpscalerFile = ({ fileName, typeName }: PlatformSpecificFileDefinition) => { const [browser, node] = tfjsLibraries.map(tfjsLibrary => getPlatformSpecificUpscalerDeclarationReflections(tfjsLibrary, { fileName, typeName })); - if (browser.type !== node.type) { - throw new Error('Some mismatch between browser and node types'); + if (browser.type?.type !== node.type?.type) { + throw new Error([ + 'Some mismatch for file name', + fileName, + 'and type name', + typeName, + 'between browser type:', + `\n\n${JSON.stringify(browser.type)}\n\n`, + 'and node type:', + `\n\n${JSON.stringify(node.type)}`, + ].join(' ')); + } + + const type = browser.type; + if (!type) { + throw new Error('No type defined on browser type'); } return { - declarationReflection: makeDeclarationReflection(typeName, ReflectionKind.Function, browser.type), + declarationReflection: makeDeclarationReflection(typeName, type), browser, node, }; diff --git a/internals/cli/src/lib/commands/write/docs/api/get-sorted-methods-for-writing.ts b/internals/cli/src/lib/commands/write/docs/api/get-sorted-methods-for-writing.ts index 09a6046e4..8c18652f8 100644 --- a/internals/cli/src/lib/commands/write/docs/api/get-sorted-methods-for-writing.ts +++ b/internals/cli/src/lib/commands/write/docs/api/get-sorted-methods-for-writing.ts @@ -1,7 +1,6 @@ import { DeclarationReflection } from "typedoc"; -import { DecRef, Definitions } from "./types.js"; +import { DecRef, Definitions, isPlatformSpecificFileDeclarationReflection } from "./types.js"; import { VALID_EXPORTS_FOR_WRITING_DOCS, VALID_METHODS_FOR_WRITING_DOCS } from "./constants.js"; -import { isPlatformSpecificFileDeclarationReflection } from "./get-definitions/get-types-from-platform-specific-upscaler-files.js"; import { info } from "@internals/common/logger"; import { sortChildrenByLineNumber } from "./sort-children-by-line-number.js"; diff --git a/internals/cli/src/lib/commands/write/docs/api/types.ts b/internals/cli/src/lib/commands/write/docs/api/types.ts index 5a712261b..f88beaafa 100644 --- a/internals/cli/src/lib/commands/write/docs/api/types.ts +++ b/internals/cli/src/lib/commands/write/docs/api/types.ts @@ -8,7 +8,12 @@ import { SomeType, UnionType, } from "typedoc"; -import { PlatformSpecificFileDeclarationReflection } from './get-definitions/get-types-from-platform-specific-upscaler-files.js'; + +export interface PlatformSpecificFileDeclarationReflection { + declarationReflection: DeclarationReflection; + browser: DeclarationReflection; + node: DeclarationReflection; +} export type DecRef = DeclarationReflection | PlatformSpecificFileDeclarationReflection; export interface Definitions { @@ -21,7 +26,11 @@ export interface Definitions { enums: Record; } -export const isDeclarationReflection = (reflection?: DecRef): reflection is DeclarationReflection => reflection?.kind !== 'Platform Specific Type'; +export const isPlatformSpecificFileDeclarationReflection = ( + child: DeclarationReflection | PlatformSpecificFileDeclarationReflection +): child is PlatformSpecificFileDeclarationReflection => 'browser' in child; + +export const isDeclarationReflection = (reflection?: DecRef): reflection is DeclarationReflection => reflection !== undefined && !isPlatformSpecificFileDeclarationReflection(reflection); export const isArrayType = (type: SomeType): type is ArrayType => type.type === 'array'; export const isReferenceType = (type: SomeType): type is ReferenceType => type.type === 'reference'; export const isLiteralType = (type: SomeType): type is LiteralType => type.type === 'literal'; diff --git a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-content-for-method.ts b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-content-for-method.ts index 793a0f351..c6c8e6c24 100644 --- a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-content-for-method.ts +++ b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-content-for-method.ts @@ -1,11 +1,10 @@ -import { TYPES_TO_EXPAND } from './constants.js'; import { DeclarationReflection, SignatureReflection, TypeParameterReflection } from 'typedoc'; -import { Definitions } from './types.js'; import { getSource } from './get-source.js'; import { getTextSummary } from './get-text-summary.js'; import { getParameters } from './write-parameter.js'; import { getReturnType } from './get-return-type.js'; -import { EXPANDED_TYPE_CONTENT } from '../constants.js'; +import { EXPANDED_TYPE_CONTENT, TYPES_TO_EXPAND } from '../constants.js'; +import { Definitions } from '../types.js'; function getAsObj (arr: T[], getKey: (item: T) => string) { return arr.reduce((obj, item) => ({ diff --git a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-matching-type.test.ts b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-matching-type.test.ts new file mode 100644 index 000000000..30fc8a673 --- /dev/null +++ b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-matching-type.test.ts @@ -0,0 +1,189 @@ +import { ArrayType, DeclarationReflection, Reflection, ReflectionKind, TypeParameterReflection } from "typedoc"; +import { getMatchingType } from "./get-matching-type.js"; +import { getReferenceTypeOfParameter } from "./get-reference-type-of-parameter.js"; +import * as constants from "../constants.js"; +import { Definitions, isLiteralType } from "../types.js"; + +vi.mock('../types.js', async () => { + const actualTypes = await import('../types.js'); + return { + ...actualTypes, + isLiteralType: vi.fn().mockImplementation(() => false), + } +}); + +vi.mock('../constants.js', () => ({ + INTRINSIC_TYPES: [], + EXTERNALLY_DEFINED_TYPES: [], +})); + +vi.mock('./get-reference-type-of-parameter.js', () => ({ + getReferenceTypeOfParameter: vi.fn(), +})); + +const makeNewExternalType = (name: string, _url: string): DeclarationReflection => { + const type = new DeclarationReflection(name, ReflectionKind['SomeType']); + type.sources = []; + return type; +}; + +const Interface = { + name: 'Interface', + kind: ReflectionKind.Interface, +}; +const TypeAlias = { + name: 'TypeAlias', + kind: ReflectionKind.TypeAlias, +}; +const mockDefinitions = (): Definitions => { + const Constructor = { + name: 'Constructor', + kind: ReflectionKind.Constructor, + }; + const Method = { + name: 'Method', + kind: ReflectionKind.Method, + }; + const Class = { + name: 'Class', + kind: ReflectionKind.Class, + }; + const Function = { + name: 'Function', + kind: ReflectionKind.Function, + }; + const Enum = { + name: 'Enum', + kind: ReflectionKind.Enum, + }; + const PlatformSpecific = { + declarationReflection: { + name: 'PlatformSpecific', + kind: ReflectionKind.Constructor, + }, + browser: {}, + node: {}, + }; + return { + methods: { + Method, + }, + constructors: { + Constructor, + PlatformSpecific, + }, + functions: { + Function, + }, + types: { + TypeAlias, + }, + interfaces: { + Interface, + }, + classes: { + Class: Class, + }, + enums: { + Enum, + }, + } as unknown as Definitions; +} + +describe('getMatchingType', () => { + beforeEach(() => { + vi.spyOn(constants, 'INTRINSIC_TYPES', 'get').mockReturnValue([]); + vi.spyOn(constants, 'EXTERNALLY_DEFINED_TYPES', 'get').mockReturnValue({}); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('returns undefined if intrinsic types includes the name of the type definition', () => { + const definitions = mockDefinitions(); + const parameter = new DeclarationReflection('foo', ReflectionKind.Parameter); + constants.INTRINSIC_TYPES.push('foo'); + vi.mocked(getReferenceTypeOfParameter).mockImplementation(() => ({ + name: 'foo', + type: 'literal' as 'literal', + })); + expect(getMatchingType(parameter, definitions, {})).toEqual(undefined); + }); + + it('returns undefined if parameter type is undefined', () => { + const definitions = mockDefinitions(); + const parameter = new DeclarationReflection('foo', ReflectionKind.Parameter); + parameter.type = undefined; + vi.mocked(getReferenceTypeOfParameter).mockImplementation(() => ({ + name: 'foo', + type: 'literal' as 'literal', + })); + expect(getMatchingType(parameter, definitions, {})).toEqual(undefined); + }); + + it('returns undefined if it is a literal type', () => { + const definitions = mockDefinitions(); + const parameter = new DeclarationReflection('foo', ReflectionKind.Parameter); + parameter.type = { type: 'array' } as ArrayType; + vi.mocked(getReferenceTypeOfParameter).mockImplementation(() => ({ + name: 'foo', + type: 'literal' as 'literal', + })); + vi.mocked(isLiteralType).mockImplementation(() => true) + expect(getMatchingType(parameter, definitions, {})).toEqual(undefined); + }); + + it('returns an externally defined type if defined', () => { + const definitions = mockDefinitions(); + const parameter = new DeclarationReflection('foo', ReflectionKind.Parameter); + parameter.type = { type: 'array' } as ArrayType; + const externalType = makeNewExternalType('Foo', 'https://foo.com'); + vi.spyOn(constants, 'EXTERNALLY_DEFINED_TYPES', 'get').mockReturnValue({ + Foo: externalType, + }); + vi.mocked(getReferenceTypeOfParameter).mockImplementation(() => ({ + name: 'Foo', + type: 'literal' as 'literal', + })); + expect(getMatchingType(parameter, definitions, {})).toEqual(externalType); + }); + + it('returns a type defined on an interface', () => { + const definitions = mockDefinitions(); + const parameter = new DeclarationReflection('foo', ReflectionKind.Parameter); + parameter.type = { type: 'array' } as ArrayType; + vi.mocked(getReferenceTypeOfParameter).mockImplementation(() => ({ + name: Interface.name, + type: 'literal' as 'literal', + })); + expect(getMatchingType(parameter, definitions, {})).toEqual(Interface); + }); + + it('returns a type defined on a types', () => { + const definitions = mockDefinitions(); + const parameter = new DeclarationReflection('foo', ReflectionKind.Parameter); + parameter.type = { type: 'array' } as ArrayType; + vi.mocked(getReferenceTypeOfParameter).mockImplementation(() => ({ + name: TypeAlias.name, + type: 'literal' as 'literal', + })); + expect(getMatchingType(parameter, definitions, {})).toEqual(TypeAlias); + }); + + // it('returns a type parameter if defined', () => { + // const definitions = mockDefinitions(); + // const parameter = new DeclarationReflection('parameter', ReflectionKind.Parameter); + // // parameter.type = 'foo'; + // vi.mocked(getReferenceTypeOfParameter).mockImplementation(() => ({ + // name: 'foo', + // type: 'literal' as 'literal', + // })); + // const reflection = new DeclarationReflection('bar', ReflectionKind.Class); + // const typeReflection = new TypeParameterReflection('foo', reflection, undefined); + // expect(getMatchingType(parameter, definitions, { + // foo: typeReflection, + // })).toEqual(); + // }); +}); + diff --git a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-matching-type.ts b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-matching-type.ts index c3ccc70f0..c632dd74a 100644 --- a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-matching-type.ts +++ b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/get-matching-type.ts @@ -1,38 +1,64 @@ import { DeclarationReflection, ParameterReflection, TypeParameterReflection } from "typedoc"; -import { Definitions } from "../types.js"; +import { DecRef, Definitions, isLiteralType, isPlatformSpecificFileDeclarationReflection, isUnionType } from "../types.js"; +import { EXTERNALLY_DEFINED_TYPES, INTRINSIC_TYPES } from "../constants.js"; +import { warn } from "@internals/common/logger"; +import { getReferenceTypeOfParameter } from "./get-reference-type-of-parameter.js"; + +/** + * getMatchingType returns the matching type for a given parameter. + * + * It looks through the definitions tree, along with externally defined types + */ export const getMatchingType = ( parameter: ParameterReflection | DeclarationReflection, definitions: Definitions, typeParameters: Record = {} -) => { +): undefined | DecRef => { const { classes, interfaces, types } = definitions; let { name: nameOfTypeDefinition } = getReferenceTypeOfParameter(parameter.type, definitions); - let matchingType: undefined | PlatformSpecificDeclarationReflection | DeclarationReflection | TypeParameterReflection; - if (!INTRINSIC_TYPES.includes(nameOfTypeDefinition) && parameter.type !== undefined && !isLiteralType(parameter.type)) { - // first, check if it is a specially defined external type - matchingType = EXTERNALLY_DEFINED_TYPES[nameOfTypeDefinition] || interfaces[nameOfTypeDefinition] || types[nameOfTypeDefinition]; - // console.log('matchingType', matchingType); - if (!matchingType) { - // it's possible that this type is a generic type; in which case, replace the generic with the actual type it's extending - matchingType = typeParameters[nameOfTypeDefinition]; - if (matchingType) { - nameOfTypeDefinition = (matchingType as any).type.name; - matchingType = interfaces[nameOfTypeDefinition] || types[nameOfTypeDefinition]; - parameter.type = matchingType.type; + if (INTRINSIC_TYPES.includes(nameOfTypeDefinition)) { + return undefined; + } + if (parameter.type === undefined) { + return undefined; + } + if (isLiteralType(parameter.type)) { + return undefined; + } + // first, check if it is a specially defined external type + const externallyDefinedType = EXTERNALLY_DEFINED_TYPES[nameOfTypeDefinition] || interfaces[nameOfTypeDefinition] || types[nameOfTypeDefinition]; + if (externallyDefinedType) { + return externallyDefinedType; + } + + // it's possible that this type is a generic type; in which case, replace the generic with the actual type it's extending + // this is _UGLY_ + const typeParameterType = typeParameters[nameOfTypeDefinition]; + if (typeParameterType && typeParameterType.type !== undefined && 'name' in typeParameterType.type) { + nameOfTypeDefinition = typeParameterType.type.name; + const matchingType = interfaces[nameOfTypeDefinition] || types[nameOfTypeDefinition]; + parameter.type = isPlatformSpecificFileDeclarationReflection(matchingType) ? matchingType.declarationReflection.type : matchingType.type; + return matchingType; + } + if (!isUnionType(parameter.type)) { + let matchingDefKey: string | undefined; + for (const [definitionKey, defs] of Object.entries(definitions)) { + for (const key of Object.keys(defs)) { + if (key === nameOfTypeDefinition) { + matchingDefKey = definitionKey; + } } } - if (!matchingType && (parameter.type === undefined || !isUnionType(parameter.type))) { - console.warn('------') - console.warn(parameter.type); - console.warn([ - `No matching type could be found for ${nameOfTypeDefinition}.`, - `- Available interfaces: ${Object.keys(interfaces).join(', ')}`, - `- Available types: ${Object.keys(types).join(', ')}`, - `- Available classes: ${Object.keys(classes).join(', ')}` - ].join('\n')); - console.warn('------') - } + warn([ + `No matching type could be found for ${nameOfTypeDefinition} in interfaces, types, or classes.`, + matchingDefKey ? `However, it was found in ${matchingDefKey}.` : undefined, + `- Available interfaces: ${Object.keys(interfaces).join(', ')}`, + `- Available types: ${Object.keys(types).join(', ')}`, + `- Available classes: ${Object.keys(classes).join(', ')}`, + 'Parameter type:', + JSON.stringify(parameter.type), + ].join('\n')); } - return matchingType; + return undefined; }; diff --git a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-api-documentation-files.ts b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-api-documentation-files.ts index 1403276d2..08f8b5717 100644 --- a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-api-documentation-files.ts +++ b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-api-documentation-files.ts @@ -3,16 +3,20 @@ import { mkdirp, writeFile } from '@internals/common/fs'; import { getContentForMethod } from './get-content-for-method.js'; import { Definitions } from '../types.js'; import { DeclarationReflection } from 'typedoc'; +import { info, verbose } from '@internals/common/logger'; export const writeAPIDocumentationFiles = async (dest: string, methods: DeclarationReflection[], definitions: Definitions) => { await Promise.all(methods.map(async (method, i) => { + verbose('Getting content for method', method.name); const content = await getContentForMethod(method, definitions, i); + verbose('Content for method', method.name, 'measures', content.length); if (!content) { throw new Error(`No content for method ${method.name}`); } const target = path.resolve(dest, `${method.name}.md`); await mkdirp(path.dirname(target)); await writeFile(target, content.trim()); + verbose('Wrote content for method', method.name, 'to', target); })); }; diff --git a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-parameter.ts b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-parameter.ts index 4c9497ada..9316c81fd 100644 --- a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-parameter.ts +++ b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-parameter.ts @@ -1,15 +1,23 @@ -import { DeclarationReflection, ParameterReflection } from "typedoc"; -import { PlatformSpecificFileDeclarationReflection } from "../get-definitions/get-types-from-platform-specific-upscaler-files.js"; -import { Definitions, isDeclarationReflection } from "../types.js"; +import { DeclarationReflection, ParameterReflection, TypeParameterReflection } from "typedoc"; +import { DecRef, Definitions, PlatformSpecificFileDeclarationReflection, isDeclarationReflection, isPlatformSpecificFileDeclarationReflection } from "../types.js"; import { getReferenceTypeOfParameter } from "./get-reference-type-of-parameter.js"; import { getURLFromSources } from "./get-url-from-sources.js"; import { sortChildrenByLineNumber } from "../sort-children-by-line-number.js"; import { getMatchingType } from "./get-matching-type.js"; import { TYPES_TO_EXPAND } from "../constants.js"; -const getSummary = (comment?: any) => comment?.summary.map(({ text }: any) => text).join(''); +export const getSummary = (comment?: any) => comment?.summary.map(({ text }: any) => text).join(''); -const writeParameter = (methodName: string, parameter: ParameterReflection | DeclarationReflection, matchingType: undefined | DecRef | TypeParameterReflection, definitions: Definitions, childParameters: string) => { +type MatchingType = undefined | DecRef | TypeParameterReflection; +type Parameter = ParameterReflection | DeclarationReflection; + +const writeParameter = ( + methodName: string, + parameter: Parameter, + matchingType: MatchingType, + definitions: Definitions, + childParameters: string +) => { const comment = getSummary(parameter.comment); const { type, name, includeURL = true } = getReferenceTypeOfParameter(parameter.type, definitions); const parsedName = `${name}${type === 'array' ? '[]' : ''}`; @@ -57,17 +65,25 @@ export const writePlatformSpecificDefinitions = (definitions: Definitions): stri ].join('\n')).join('\n'); }; -export const getParameters = (methodName: string, parameters: (ParameterReflection | DeclarationReflection)[], definitions: Definitions, typeParameters: Record = {}, depth = 0): string => { +export const getParameters = ( + methodName: string, + parameters: Parameter[], + definitions: Definitions, + typeParameters: Record = {}, + depth = 0 +): string => { if (depth > 5) { throw new Error('Too many levels of depth'); } return parameters.map((parameter) => { - const matchingType: any = getMatchingType(parameter, definitions, typeParameters); - const { children = [] } = matchingType || {}; - const childParameters = getParameters(methodName, sortChildrenByLineNumber(children), definitions, typeParameters, depth + 1); - return [ - writeParameter(methodName, parameter, matchingType, definitions, childParameters), - childParameters, - ].filter(Boolean).map(line => Array(depth * 2).fill(' ').join('') + line).join('\n'); + const matchingType = getMatchingType(parameter, definitions, typeParameters); + if (matchingType) { + const { children } = isPlatformSpecificFileDeclarationReflection(matchingType) ? matchingType.declarationReflection : matchingType; + const childParameters = children ? getParameters(methodName, sortChildrenByLineNumber(children), definitions, typeParameters, depth + 1) : ''; + return [ + writeParameter(methodName, parameter, matchingType, definitions, childParameters), + childParameters, + ].filter(Boolean).map(line => Array(depth * 2).fill(' ').join('') + line).join('\n'); + } }).filter(Boolean).join('\n'); }; diff --git a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-platform-specific-parameter.ts b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-platform-specific-parameter.ts index efe8a5312..e77fe420e 100644 --- a/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-platform-specific-parameter.ts +++ b/internals/cli/src/lib/commands/write/docs/api/write-api-documentation-files/write-platform-specific-parameter.ts @@ -1,8 +1,11 @@ import { DeclarationReflection } from "typedoc"; import { Definitions } from "../types.js"; +import { getReferenceTypeOfParameter } from "./get-reference-type-of-parameter.js"; +import { getURLFromSources } from "./get-url-from-sources.js"; +import { getSummary } from "./write-parameter.js"; export const writePlatformSpecificParameter = (platform: string, parameter: DeclarationReflection, definitions: Definitions) => { - const comment = getSummary(parameter.comment as any); + const comment = getSummary(parameter.comment); const { type, name } = getReferenceTypeOfParameter(parameter.type, definitions); const url = getURLFromSources(parameter); const parsedName = `${name}${type === 'array' ? '[]' : ''}`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d353b7b7a..2c95ce891 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -630,8 +630,8 @@ importers: specifier: ^1.8.7 version: 1.8.7 typedoc: - specifier: ^0.25.1 - version: 0.25.1(typescript@5.1.6) + specifier: ^0.24.0 + version: 0.24.8(typescript@5.1.6) devDependencies: '@commander-js/extra-typings': specifier: ^11.0.0 @@ -17952,7 +17952,7 @@ packages: typedoc: 0.24.8(typescript@5.2.2) dev: true - /typedoc@0.24.8(typescript@5.2.2): + /typedoc@0.24.8(typescript@5.1.6): resolution: {integrity: sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==} engines: {node: '>= 14.14'} hasBin: true @@ -17963,22 +17963,22 @@ packages: marked: 4.3.0 minimatch: 9.0.3 shiki: 0.14.4 - typescript: 5.2.2 - dev: true + typescript: 5.1.6 + dev: false - /typedoc@0.25.1(typescript@5.1.6): - resolution: {integrity: sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==} - engines: {node: '>= 16'} + /typedoc@0.24.8(typescript@5.2.2): + resolution: {integrity: sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==} + engines: {node: '>= 14.14'} 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 + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x dependencies: lunr: 2.3.9 marked: 4.3.0 minimatch: 9.0.3 shiki: 0.14.4 - typescript: 5.1.6 - dev: false + typescript: 5.2.2 + dev: true /typescript@5.1.6: resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}