// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. import { TSDocConfiguration } from '@microsoft/tsdoc'; import type { IExcerptToken } from '../index.js'; import { ExcerptTokenKind } from '../index.js'; import type { IApiDeclaredItemJson } from '../items/ApiDeclaredItem.js'; import type { IApiDocumentedItemJson } from '../items/ApiDocumentedItem.js'; import { type IApiItemJson, type IApiItemOptions, type ApiItem, ApiItemKind } from '../items/ApiItem.js'; import type { IApiPropertyItemJson } from '../items/ApiPropertyItem.js'; import type { IApiAbstractMixinJson } from '../mixins/ApiAbstractMixin.js'; import type { IApiItemContainerJson } from '../mixins/ApiItemContainerMixin.js'; import type { IApiNameMixinJson } from '../mixins/ApiNameMixin.js'; import type { IApiOptionalMixinJson } from '../mixins/ApiOptionalMixin.js'; import type { IApiParameterListJson, IApiParameterOptions } from '../mixins/ApiParameterListMixin.js'; import type { IApiProtectedMixinJson } from '../mixins/ApiProtectedMixin.js'; import type { IApiReadonlyMixinJson } from '../mixins/ApiReadonlyMixin.js'; import type { IApiReleaseTagMixinJson } from '../mixins/ApiReleaseTagMixin.js'; import type { IApiReturnTypeMixinJson } from '../mixins/ApiReturnTypeMixin.js'; import type { IApiStaticMixinJson } from '../mixins/ApiStaticMixin.js'; import type { IApiTypeParameterListMixinJson } from '../mixins/ApiTypeParameterListMixin.js'; import { ApiCallSignature, type IApiCallSignatureOptions } from './ApiCallSignature.js'; import { ApiClass, type IApiClassOptions, type IApiClassJson } from './ApiClass.js'; import { ApiConstructSignature, type IApiConstructSignatureOptions } from './ApiConstructSignature.js'; import { ApiConstructor, type IApiConstructorOptions } from './ApiConstructor.js'; import { ApiEntryPoint, type IApiEntryPointOptions } from './ApiEntryPoint.js'; import { ApiEnum, type IApiEnumOptions } from './ApiEnum.js'; import { ApiEnumMember, type IApiEnumMemberOptions } from './ApiEnumMember.js'; import type { IApiEventOptions } from './ApiEvent.js'; import { ApiEvent } from './ApiEvent.js'; import { ApiFunction, type IApiFunctionOptions } from './ApiFunction.js'; import { ApiIndexSignature, type IApiIndexSignatureOptions } from './ApiIndexSignature.js'; import { ApiInterface, type IApiInterfaceOptions, type IApiInterfaceJson } from './ApiInterface.js'; import { ApiMethod, type IApiMethodOptions } from './ApiMethod.js'; import { ApiMethodSignature, type IApiMethodSignatureOptions } from './ApiMethodSignature.js'; import { ApiModel } from './ApiModel.js'; import { ApiNamespace, type IApiNamespaceOptions } from './ApiNamespace.js'; import { ApiPackage, type IApiPackageOptions, type IApiPackageJson } from './ApiPackage.js'; import { ApiProperty, type IApiPropertyOptions } from './ApiProperty.js'; import { ApiPropertySignature, type IApiPropertySignatureOptions } from './ApiPropertySignature.js'; import { ApiTypeAlias, type IApiTypeAliasOptions, type IApiTypeAliasJson } from './ApiTypeAlias.js'; import { ApiVariable, type IApiVariableOptions, type IApiVariableJson } from './ApiVariable.js'; import { ApiJsonSchemaVersion, type DeserializerContext } from './DeserializerContext.js'; type DocgenAccess = 'private' | 'protected' | 'public'; type DocgenScope = 'global' | 'instance' | 'static'; type DocgenDeprecated = boolean | string; interface DocgenMetaJson { file: string; line: number; path: string; } interface DocgenTypeJson { names?: string[] | undefined; } interface DocgenVarJson { description?: string; nullable?: boolean; types?: string[][][]; } type DocgenVarTypeJson = DocgenVarJson | string[][][]; interface DocgenExceptionJson { description?: string; nullable?: boolean; type: DocgenTypeJson; } interface DocgenExternalJson { description: string; meta: DocgenMetaJson; name: string; see?: string[]; } interface DocgenTypedefJson { access?: DocgenAccess; deprecated?: DocgenDeprecated; description: string; meta: DocgenMetaJson; name: string; params?: DocgenParamJson[]; props?: DocgenParamJson[]; returns?: DocgenVarTypeJson[]; see?: string[]; type: DocgenVarTypeJson; } interface DocgenEventJson { deprecated?: DocgenDeprecated; description: string; meta: DocgenMetaJson; name: string; params?: DocgenParamJson[]; see?: string[]; } interface DocgenParamJson { default?: string; description: string; name: string; nullable?: boolean; optional?: boolean; type: DocgenVarTypeJson; variable?: string; } interface DocgenConstructorJson { access?: DocgenAccess; description: string; name: string; params?: DocgenParamJson[]; see?: string[]; } interface DocgenMethodJson { abstract: boolean; access?: DocgenAccess; async?: boolean; deprecated?: DocgenDeprecated; description: string; emits?: string[]; examples?: string[]; generator?: boolean; implements?: string[]; inherited?: boolean; inherits?: string; meta: DocgenMetaJson; name: string; params?: DocgenParamJson[]; returns?: DocgenVarTypeJson[]; scope: DocgenScope; see?: string[]; throws?: DocgenExceptionJson[]; } interface DocgenPropertyJson { abstract?: boolean; access?: DocgenAccess; default?: string; deprecated?: DocgenDeprecated; description: string; meta: DocgenMetaJson; name: string; nullable?: boolean; props?: DocgenPropertyJson[]; readonly?: boolean; scope: DocgenScope; see?: string[]; type: DocgenVarTypeJson; } interface DocgenClassJson { abstract?: boolean; access?: DocgenAccess; construct: DocgenConstructorJson; deprecated?: DocgenDeprecated | string; description: string; events?: DocgenEventJson[]; extends?: DocgenVarTypeJson; implements?: DocgenVarTypeJson; meta: DocgenMetaJson; methods?: DocgenMethodJson[]; name: string; props?: DocgenPropertyJson[]; see?: string[]; } type DocgenInterfaceJson = DocgenClassJson; export interface DocgenJson { classes: DocgenClassJson[]; externals: DocgenExternalJson[]; functions: DocgenMethodJson[]; interfaces: DocgenInterfaceJson[]; meta: { date: number; format: number; generator: string; }; typedefs: DocgenTypedefJson[]; } function formatVarType(type: DocgenVarTypeJson): string { return (Array.isArray(type) ? type : (type.types ?? [])) .map((t1) => t1.map((t2) => t2.join('')).join('')) .join(' | '); } function getFirstType(type: DocgenVarTypeJson): string { return (Array.isArray(type) ? type[0]?.[0]?.[0] : type.types?.[0]?.[0]?.[0]) ?? 'unknown'; } // function mapEvent(_event: DocgenEventJson, _package: string, _parent: DocgenClassJson): void {} function mapVarType(type: DocgenVarTypeJson, _package: string): IExcerptToken[] { const mapper = Array.isArray(type) ? type : (type.types ?? []); return mapper.flatMap((typ) => typ.reduce( (arr, [_class, symbol]) => [ ...arr, { kind: ExcerptTokenKind.Reference, text: _class ?? 'unknown', canonicalReference: `${_package}!${_class}:class`, }, { kind: ExcerptTokenKind.Content, text: symbol ?? '' }, ], [], ), ); } function mapProp( prop: DocgenPropertyJson, _package: string, parent: DocgenClassJson | DocgenInterfaceJson, ): IApiNameMixinJson & IApiOptionalMixinJson & IApiPropertyItemJson & IApiReadonlyMixinJson & IApiReleaseTagMixinJson { const mappedVarType = mapVarType(prop.type, _package); return { kind: ApiItemKind.Property, name: prop.name, isOptional: Boolean(prop.nullable), isReadonly: Boolean(prop.readonly), docComment: `/**\n * ${prop.description}\n${prop.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''}${ prop.readonly ? ' * @readonly\n' : '' } */`, excerptTokens: [ { kind: ExcerptTokenKind.Content, text: `${prop.access} ${prop.scope === 'static' ? 'static ' : ''}${prop.readonly ? 'readonly ' : ''}${ prop.name } :`, }, ...mappedVarType, { kind: ExcerptTokenKind.Reference, text: formatVarType(prop.type), canonicalReference: `${_package}!${getFirstType(prop.type)}:class`, }, { kind: ExcerptTokenKind.Content, text: ';' }, ], propertyTypeTokenRange: { startIndex: 1, endIndex: 1 + mappedVarType.length }, canonicalReference: `${_package}!${parent.name}#${prop.name}:member`, releaseTag: prop.access === 'public' ? 'Public' : 'Internal', fileLine: prop.meta.line, fileUrlPath: `${prop.meta.path.slice(`packages/${_package}/`.length)}/${prop.meta.file}`, }; } function mapParam( param: DocgenParamJson, index: number, _package: string, paramTokens: number[], ): IApiParameterOptions { return { parameterName: param.name.startsWith('...') ? param.name.slice(3) : param.name, isOptional: Boolean(param.optional), isRest: param.name.startsWith('...'), parameterTypeTokenRange: { startIndex: 1 + index + paramTokens.slice(0, index).reduce((akk, num) => akk + num, 0), endIndex: 1 + index + paramTokens.slice(0, index + 1).reduce((akk, num) => akk + num, 0), }, defaultValue: param.default, }; } interface IApiMethodJson extends IApiAbstractMixinJson, IApiDeclaredItemJson, IApiNameMixinJson, IApiOptionalMixinJson, IApiParameterListJson, IApiProtectedMixinJson, IApiReleaseTagMixinJson, IApiReturnTypeMixinJson, IApiStaticMixinJson, IApiTypeParameterListMixinJson {} interface IApiConstructorJson extends IApiParameterListJson, IApiProtectedMixinJson, IApiReleaseTagMixinJson, IApiDeclaredItemJson {} function mapMethod(method: DocgenMethodJson, _package: string, parent?: DocgenClassJson): IApiMethodJson { const excerptTokens: IExcerptToken[] = []; excerptTokens.push({ kind: ExcerptTokenKind.Content, text: `${ method.scope === 'global' ? `export function ${method.name}(` : `${method.access}${method.scope === 'static' ? ' static' : ''} ${method.name}(` }${ method.params?.length ? `${method.params[0]!.name}${method.params[0]!.nullable || method.params[0]!.optional ? '?' : ''}` : '): ' }`, }); const paramTokens: number[] = []; for (let index = 0; index < (method.params?.length ?? 0) - 1; index++) { const newTokens = mapVarType(method.params![index]!.type, _package); paramTokens.push(newTokens.length); excerptTokens.push(...newTokens); excerptTokens.push({ kind: ExcerptTokenKind.Content, text: `, ${method.params![index + 1]!.name}${ method.params![index + 1]!.nullable || method.params![index + 1]!.optional ? '?' : '' }: `, }); } if (method.params?.length) { const newTokens = mapVarType(method.params[method.params.length - 1]!.type, _package); paramTokens.push(newTokens.length); excerptTokens.push(...newTokens); excerptTokens.push({ kind: ExcerptTokenKind.Content, text: `): ` }); } const returnTokens = mapVarType(method.returns?.[0] ?? [], _package); excerptTokens.push(...returnTokens); excerptTokens.push({ kind: ExcerptTokenKind.Content, text: ';' }); return { kind: parent ? ApiItemKind.Method : ApiItemKind.Function, name: method.name, isAbstract: method.abstract, isOptional: false, isProtected: method.access === 'protected', isStatic: method.scope === 'static', canonicalReference: `${_package}!${parent ? `${parent.name}!${method.name}:member` : `${method.name}:function`}`, overloadIndex: 1, parameters: method.params?.map((param, index) => mapParam(param, index, _package, paramTokens)) ?? [], releaseTag: method.access === 'public' ? 'Public' : 'Internal', returnTypeTokenRange: method.returns?.length ? method.params?.length ? { startIndex: 2 + 2 * method.params.length, endIndex: 3 + 2 * method.params.length } : { startIndex: 1, endIndex: 2 } : { startIndex: 0, endIndex: 0 }, typeParameters: [], docComment: `/**\n * ${method.description}\n${ method.params?.map((param) => ` * @param ${param.name} - ${param.description}\n`).join('') ?? '' }${ method.returns?.length && !Array.isArray(method.returns[0]) ? ` * @returns ${method.returns[0]!.description}` : '' } */`, excerptTokens, fileLine: method.meta.line, fileUrlPath: `${method.meta.path.slice(`packages/${_package}/`.length)}/${method.meta.file}`, }; } export class Deserializer { public static deserialize(context: DeserializerContext, jsonObject: IApiItemJson): ApiItem { const options: Partial = {}; switch (jsonObject.kind) { case ApiItemKind.Class: ApiClass.onDeserializeInto(options, context, jsonObject as IApiClassJson); return new ApiClass(options as IApiClassOptions); case ApiItemKind.CallSignature: ApiCallSignature.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiCallSignature(options as IApiCallSignatureOptions); case ApiItemKind.Constructor: ApiConstructor.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiConstructor(options as IApiConstructorOptions); case ApiItemKind.ConstructSignature: ApiConstructSignature.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiConstructSignature(options as IApiConstructSignatureOptions); case ApiItemKind.EntryPoint: ApiEntryPoint.onDeserializeInto(options, context, jsonObject); return new ApiEntryPoint(options as IApiEntryPointOptions); case ApiItemKind.Enum: ApiEnum.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiEnum(options as IApiEnumOptions); case ApiItemKind.EnumMember: ApiEnumMember.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiEnumMember(options as IApiEnumMemberOptions); case ApiItemKind.Event: ApiEvent.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiEvent(options as IApiEventOptions); case ApiItemKind.Function: ApiFunction.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiFunction(options as IApiFunctionOptions); case ApiItemKind.IndexSignature: ApiIndexSignature.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiIndexSignature(options as IApiIndexSignatureOptions); case ApiItemKind.Interface: ApiInterface.onDeserializeInto(options, context, jsonObject as IApiInterfaceJson); return new ApiInterface(options as IApiInterfaceOptions); case ApiItemKind.Method: ApiMethod.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiMethod(options as IApiMethodOptions); case ApiItemKind.MethodSignature: ApiMethodSignature.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiMethodSignature(options as IApiMethodSignatureOptions); case ApiItemKind.Model: return new ApiModel(); case ApiItemKind.Namespace: ApiNamespace.onDeserializeInto(options, context, jsonObject as IApiDeclaredItemJson); return new ApiNamespace(options as IApiNamespaceOptions); case ApiItemKind.Package: ApiPackage.onDeserializeInto(options, context, jsonObject as IApiPackageJson); return new ApiPackage(options as IApiPackageOptions); case ApiItemKind.Property: ApiProperty.onDeserializeInto(options, context, jsonObject as IApiPropertyItemJson); return new ApiProperty(options as IApiPropertyOptions); case ApiItemKind.PropertySignature: ApiPropertySignature.onDeserializeInto(options, context, jsonObject as IApiPropertyItemJson); return new ApiPropertySignature(options as IApiPropertySignatureOptions); case ApiItemKind.TypeAlias: ApiTypeAlias.onDeserializeInto(options, context, jsonObject as IApiTypeAliasJson); return new ApiTypeAlias(options as IApiTypeAliasOptions); case ApiItemKind.Variable: ApiVariable.onDeserializeInto(options, context, jsonObject as IApiVariableJson); return new ApiVariable(options as IApiVariableOptions); default: throw new Error(`Failed to deserialize unsupported API item type ${JSON.stringify(jsonObject.kind)}`); } } public static deserializeDocgen(jsonObject: DocgenJson, _package: string) { const context: DeserializerContext = { apiJsonFilename: 'docs.json', tsdocConfiguration: new TSDocConfiguration(), versionToDeserialize: ApiJsonSchemaVersion.V_1011, toolPackage: jsonObject.meta.generator, toolVersion: jsonObject.meta.format.toString(), }; let members: (IApiClassJson | IApiInterfaceJson | IApiMethodJson | IApiTypeAliasJson)[] = []; for (const _class of jsonObject.classes) { const classMembers: (IApiConstructorJson | IApiMethodJson | IApiPropertyItemJson)[] = [ // ..._class.events.map((event) => mapEvent(event, _package, _class)), ...(_class.props?.map((prop) => mapProp(prop, _package, _class)) ?? []), ...(_class.methods?.map((method) => mapMethod(method, _package, _class)) ?? []), ]; if (_class.construct) { const excerptTokens: IExcerptToken[] = [ { kind: ExcerptTokenKind.Content, text: `${_class.construct.access} constructor(${ _class.construct.params?.length ? `${_class.construct.params[0]?.name}: ` : ');' }`, }, ]; const paramTokens: number[] = []; for (let index = 0; index < (_class.construct.params?.length ?? 0) - 1; index++) { const newTokens = mapVarType(_class.construct.params![index]!.type, _package); paramTokens.push(newTokens.length); excerptTokens.push(...newTokens); excerptTokens.push({ kind: ExcerptTokenKind.Content, text: `, ${_class.construct.params![index + 1]?.name}: `, }); } if (_class.construct.params?.length) { const newTokens = mapVarType(_class.construct.params[_class.construct.params.length - 1]!.type, _package); paramTokens.push(newTokens.length); excerptTokens.push(...newTokens); excerptTokens.push({ kind: ExcerptTokenKind.Content, text: ');' }); } classMembers.unshift({ parameters: _class.construct.params?.map((param, index) => mapParam(param, index, _package, paramTokens)) ?? [], isProtected: _class.construct.access === 'protected', releaseTag: _class.construct.access === 'public' ? 'Public' : 'Internal', docComment: `/*+\n * ${_class.construct.description}\n${ _class.construct.params?.map((param) => ` * @param ${param.name} - ${param.description}\n`).join('') ?? '' } */`, excerptTokens, kind: ApiItemKind.Constructor, canonicalReference: `${_package}!${_class.name}:constructor`, overloadIndex: 0, }); } const excerptTokens: IExcerptToken[] = [ { kind: ExcerptTokenKind.Content, text: `${_class.access === 'public' ? 'export ' : ''}declare class ${_class.name}${ _class.extends ? ' extends ' : _class.implements ? ' implements ' : '' }`, }, ]; if (_class.extends) excerptTokens.push({ kind: ExcerptTokenKind.Reference, text: formatVarType(_class.extends) ?? '', canonicalReference: `${_package}!${getFirstType(_class.extends) ?? ''}:class`, }); if (_class.extends && _class.implements) excerptTokens.push({ kind: ExcerptTokenKind.Content, text: ' implements ' }); if (_class.implements) excerptTokens.push({ kind: ExcerptTokenKind.Reference, text: formatVarType(_class.implements) ?? '', canonicalReference: `${_package}!${getFirstType(_class.implements) ?? ''}:class`, }); members.push({ members: classMembers, kind: ApiItemKind.Class, canonicalReference: `${_package}!${_class.name}:class`, name: _class.name, extendsTokenRange: _class.extends ? { startIndex: 1, endIndex: 2, typeParameters: [] } : undefined, excerptTokens, implementsTokenRanges: _class.implements ? [{ startIndex: _class.extends ? 3 : 1, endIndex: _class.extends ? 4 : 2, typeParameters: [] }] : [], typeParameters: [], releaseTag: _class.access === 'public' ? 'Public' : 'Internal', docComment: `/**\n * ${_class.description}\n${_class.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''} */`, isExported: _class.access === 'public', isAbstract: Boolean(_class.abstract), fileLine: _class.meta.line, fileUrlPath: `${_class.meta.path.slice(`packages/${_package}/`.length)}/${_class.meta.file}`, }); } members = [ ...members, ...jsonObject.functions.map((_func) => mapMethod(_func, _package)), ...jsonObject.interfaces.map((_interface) => ({ members: [ ...(_interface.props?.map((prop) => mapProp(prop, _package, _interface)) ?? []), ...(_interface.methods?.map((method) => mapMethod(method, _package, _interface)) ?? []), ], kind: ApiItemKind.Interface, canonicalReference: `${_package}!${_interface.name}:interface`, name: _interface.name, extendsTokenRanges: [{ startIndex: 0, endIndex: 0, typeParameters: [] }], excerptTokens: [ { kind: ExcerptTokenKind.Content, text: `${_interface.access === 'public' ? 'export ' : ''}interface ${_interface.name}`, }, ], typeParameters: [], releaseTag: _interface.access === 'public' ? 'Public' : 'Internal', docComment: `/**\n * ${_interface.description}\n${ _interface.see?.map((see) => ` * @see ${see}\n`).join('') ?? '' } */`, isExported: _interface.access === 'public', fileLine: _interface.meta.line, fileUrlPath: `${_interface.meta.path.slice(`packages/${_package}/`.length)}/${_interface.meta.file}`, })), ]; const reworkedJson: IApiDocumentedItemJson & IApiItemContainerJson & IApiNameMixinJson & IApiPackageJson & { members: (IApiItemContainerJson & IApiNameMixinJson)[] } = { projectFolderUrl: `https://github.com/discordjs/discord.js/tree/main/packages/${_package}`, metadata: { ...context, tsdocConfig: context.tsdocConfiguration, schemaVersion: context.versionToDeserialize }, canonicalReference: `!${_package}`, kind: ApiItemKind.Package, name: _package, members: [ { members, name: _package, kind: ApiItemKind.EntryPoint, canonicalReference: `${_package}!`, }, ], docComment: '', }; return Deserializer.deserialize(context, reworkedJson); } }