mirror of
https://github.com/discordjs/discord.js.git
synced 2024-08-21 08:04:42 +12:00
feat: docs for mixin methods, examples (#9960)
This commit is contained in:
parent
8d04cbc203
commit
7c935dc84b
1 changed files with 440 additions and 225 deletions
|
@ -3,7 +3,18 @@
|
|||
|
||||
import { existsSync } from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import type {
|
||||
IApiMethodOptions,
|
||||
ApiItemContainerMixin,
|
||||
IApiParameterOptions,
|
||||
IExcerptTokenRange,
|
||||
IExcerptTokenRangeWithTypeParameters,
|
||||
IExcerptToken,
|
||||
IApiTypeParameterOptions,
|
||||
IApiPropertyOptions,
|
||||
} from '@discordjs/api-extractor-model';
|
||||
import {
|
||||
ApiItemKind,
|
||||
ApiModel,
|
||||
ApiClass,
|
||||
ApiPackage,
|
||||
|
@ -13,16 +24,11 @@ import {
|
|||
ApiNamespace,
|
||||
ApiInterface,
|
||||
ApiPropertySignature,
|
||||
type ApiItemContainerMixin,
|
||||
ReleaseTag,
|
||||
ApiProperty,
|
||||
ApiMethodSignature,
|
||||
type IApiParameterOptions,
|
||||
ApiEnum,
|
||||
ApiEnumMember,
|
||||
type IExcerptTokenRange,
|
||||
type IExcerptTokenRangeWithTypeParameters,
|
||||
type IExcerptToken,
|
||||
ApiConstructor,
|
||||
ApiConstructSignature,
|
||||
ApiFunction,
|
||||
|
@ -30,7 +36,6 @@ import {
|
|||
ApiVariable,
|
||||
ApiTypeAlias,
|
||||
ApiCallSignature,
|
||||
type IApiTypeParameterOptions,
|
||||
EnumMemberOrder,
|
||||
ExcerptTokenKind,
|
||||
Navigation,
|
||||
|
@ -214,6 +219,14 @@ function filePathFromJson(meta: DocgenMetaJson): string {
|
|||
return `${meta.path.slice('packages/discord.js/'.length)}/${meta.file}`;
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
export class ApiModelGenerator {
|
||||
private readonly _collector: Collector;
|
||||
|
||||
|
@ -433,6 +446,42 @@ export class ApiModelGenerator {
|
|||
name: childDeclaration.astSymbol.localName,
|
||||
});
|
||||
}
|
||||
|
||||
for (const method of (context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined)?.methods ??
|
||||
[]) {
|
||||
switch (context.parentApiItem.kind) {
|
||||
case ApiItemKind.Class:
|
||||
this._processApiMethod(null, { ...context, name: method.name });
|
||||
break;
|
||||
|
||||
case ApiItemKind.Interface:
|
||||
this._processApiMethodSignature(null, { ...context, name: method.name });
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(
|
||||
`Found docgen method not in TS typings for ApiItem of kind ${ApiItemKind[context.parentApiItem.kind]}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const property of (context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined)?.props ??
|
||||
[]) {
|
||||
switch (context.parentApiItem.kind) {
|
||||
case ApiItemKind.Class:
|
||||
this._processApiProperty(null, { ...context, name: property.name });
|
||||
break;
|
||||
|
||||
case ApiItemKind.Interface:
|
||||
this._processApiPropertySignature(null, { ...context, name: property.name });
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(
|
||||
`Found docgen property not in TS typings for ApiItem of kind ${ApiItemKind[context.parentApiItem.kind]}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _processApiCallSignature(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
|
||||
|
@ -955,17 +1004,20 @@ export class ApiModelGenerator {
|
|||
});
|
||||
}
|
||||
|
||||
private _processApiMethod(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
|
||||
private _processApiMethod(astDeclaration: AstDeclaration | null, context: IProcessAstEntityContext): void {
|
||||
const { name, parentApiItem } = context;
|
||||
const isStatic: boolean = (astDeclaration.modifierFlags & ts.ModifierFlags.Static) !== 0;
|
||||
const overloadIndex: number = this._collector.getOverloadIndex(astDeclaration);
|
||||
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined;
|
||||
const jsDoc = parent?.methods?.find((method) => method.name === name);
|
||||
const isStatic: boolean = astDeclaration
|
||||
? (astDeclaration.modifierFlags & ts.ModifierFlags.Static) !== 0
|
||||
: jsDoc?.scope === 'static';
|
||||
const overloadIndex: number = astDeclaration ? this._collector.getOverloadIndex(astDeclaration) : 1;
|
||||
const containerKey: string = ApiMethod.getContainerKey(name, isStatic, overloadIndex);
|
||||
|
||||
let apiMethod: ApiMethod | undefined = parentApiItem.tryGetMemberByKey(containerKey) as ApiMethod;
|
||||
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined;
|
||||
const jsDoc = parent?.methods?.find((method) => method.name === name);
|
||||
|
||||
if (apiMethod === undefined) {
|
||||
if (astDeclaration) {
|
||||
const methodDeclaration: ts.MethodDeclaration = astDeclaration.declaration as ts.MethodDeclaration;
|
||||
|
||||
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
|
||||
|
@ -978,19 +1030,25 @@ export class ApiModelGenerator {
|
|||
methodDeclaration.typeParameters,
|
||||
);
|
||||
|
||||
const parameters: IApiParameterOptions[] = this._captureParameters(nodesToCapture, methodDeclaration.parameters);
|
||||
const parameters: IApiParameterOptions[] = this._captureParameters(
|
||||
nodesToCapture,
|
||||
methodDeclaration.parameters,
|
||||
);
|
||||
|
||||
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
|
||||
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
|
||||
const docComment: tsdoc.DocComment | undefined = jsDoc
|
||||
? this._tsDocParser.parseString(
|
||||
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
|
||||
jsDoc.params?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`).join('') ??
|
||||
''
|
||||
jsDoc.params
|
||||
?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`)
|
||||
.join('') ?? ''
|
||||
}${
|
||||
jsDoc.returns?.length && !Array.isArray(jsDoc.returns[0])
|
||||
? ` * @returns ${fixLinkTags(jsDoc.returns[0]!.description ?? '')}`
|
||||
: ''
|
||||
}${
|
||||
jsDoc.examples?.map((example) => ` * @example\n * \`\`\`js\n * ${example}\n * \`\`\`\n`).join('') ?? ''
|
||||
}${
|
||||
jsDoc.deprecated
|
||||
? ` * @deprecated ${
|
||||
|
@ -1027,14 +1085,17 @@ export class ApiModelGenerator {
|
|||
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
|
||||
fileColumn: sourceLocation.sourceFileColumn,
|
||||
});
|
||||
} else if (jsDoc) {
|
||||
apiMethod = new ApiMethod(this._mapMethod(jsDoc, parentApiItem.getAssociatedPackage()!.name));
|
||||
}
|
||||
|
||||
parentApiItem.addMember(apiMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private _processApiMethodSignature(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
|
||||
private _processApiMethodSignature(astDeclaration: AstDeclaration | null, context: IProcessAstEntityContext): void {
|
||||
const { name, parentApiItem } = context;
|
||||
const overloadIndex: number = this._collector.getOverloadIndex(astDeclaration);
|
||||
const overloadIndex: number = astDeclaration ? this._collector.getOverloadIndex(astDeclaration) : 1;
|
||||
const containerKey: string = ApiMethodSignature.getContainerKey(name, overloadIndex);
|
||||
|
||||
let apiMethodSignature: ApiMethodSignature | undefined = parentApiItem.tryGetMemberByKey(
|
||||
|
@ -1044,6 +1105,7 @@ export class ApiModelGenerator {
|
|||
const jsDoc = parent?.methods?.find((method) => method.name === name);
|
||||
|
||||
if (apiMethodSignature === undefined) {
|
||||
if (astDeclaration) {
|
||||
const methodSignature: ts.MethodSignature = astDeclaration.declaration as ts.MethodSignature;
|
||||
|
||||
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
|
||||
|
@ -1063,8 +1125,9 @@ export class ApiModelGenerator {
|
|||
const docComment: tsdoc.DocComment | undefined = jsDoc
|
||||
? this._tsDocParser.parseString(
|
||||
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
|
||||
jsDoc.params?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`).join('') ??
|
||||
''
|
||||
jsDoc.params
|
||||
?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`)
|
||||
.join('') ?? ''
|
||||
}${
|
||||
jsDoc.returns?.length && !Array.isArray(jsDoc.returns[0])
|
||||
? ` * @returns ${fixLinkTags(jsDoc.returns[0]!.description ?? '')}`
|
||||
|
@ -1096,6 +1159,9 @@ export class ApiModelGenerator {
|
|||
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
|
||||
fileColumn: sourceLocation.sourceFileColumn,
|
||||
});
|
||||
} else if (jsDoc) {
|
||||
apiMethodSignature = new ApiMethodSignature(this._mapMethod(jsDoc, parentApiItem.getAssociatedPackage()!.name));
|
||||
}
|
||||
|
||||
parentApiItem.addMember(apiMethodSignature);
|
||||
}
|
||||
|
@ -1133,16 +1199,21 @@ export class ApiModelGenerator {
|
|||
});
|
||||
}
|
||||
|
||||
private _processApiProperty(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
|
||||
private _processApiProperty(astDeclaration: AstDeclaration | null, context: IProcessAstEntityContext): void {
|
||||
const { name, parentApiItem } = context;
|
||||
const isStatic: boolean = (astDeclaration.modifierFlags & ts.ModifierFlags.Static) !== 0;
|
||||
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | DocgenTypedefJson | undefined;
|
||||
const jsDoc = parent?.props?.find((prop) => prop.name === name);
|
||||
const isStatic: boolean = astDeclaration
|
||||
? (astDeclaration.modifierFlags & ts.ModifierFlags.Static) !== 0
|
||||
: parentApiItem.kind === ApiItemKind.Class || parentApiItem.kind === ApiItemKind.Interface
|
||||
? (jsDoc as DocgenPropertyJson).scope === 'static'
|
||||
: false;
|
||||
const containerKey: string = ApiProperty.getContainerKey(name, isStatic);
|
||||
|
||||
let apiProperty: ApiProperty | undefined = parentApiItem.tryGetMemberByKey(containerKey) as ApiProperty;
|
||||
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | DocgenTypedefJson | undefined;
|
||||
const jsDoc = parent?.props?.find((prop) => prop.name === name);
|
||||
|
||||
if (apiProperty === undefined) {
|
||||
if (astDeclaration) {
|
||||
const declaration: ts.Declaration = astDeclaration.declaration;
|
||||
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
|
||||
|
||||
|
@ -1204,6 +1275,14 @@ export class ApiModelGenerator {
|
|||
fileLine: jsDoc && 'meta' in jsDoc ? jsDoc.meta.line : sourceLocation.sourceFileLine,
|
||||
fileColumn: sourceLocation.sourceFileColumn,
|
||||
});
|
||||
} else if (parentApiItem.kind === ApiItemKind.Class || parentApiItem.kind === ApiItemKind.Interface) {
|
||||
apiProperty = new ApiProperty(
|
||||
this._mapProp(jsDoc as DocgenPropertyJson, parentApiItem.getAssociatedPackage()!.name),
|
||||
);
|
||||
} else {
|
||||
console.log(`We got a property in ApiItem of kind ${ApiItemKind[parentApiItem.kind]}`);
|
||||
}
|
||||
|
||||
parentApiItem.addMember(apiProperty);
|
||||
} else {
|
||||
// If the property was already declared before (via a merged interface declaration),
|
||||
|
@ -1211,7 +1290,7 @@ export class ApiModelGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
private _processApiPropertySignature(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
|
||||
private _processApiPropertySignature(astDeclaration: AstDeclaration | null, context: IProcessAstEntityContext): void {
|
||||
const { name, parentApiItem } = context;
|
||||
const containerKey: string = ApiPropertySignature.getContainerKey(name);
|
||||
|
||||
|
@ -1222,6 +1301,7 @@ export class ApiModelGenerator {
|
|||
const jsDoc = parent?.props?.find((prop) => prop.name === name);
|
||||
|
||||
if (apiPropertySignature === undefined) {
|
||||
if (astDeclaration) {
|
||||
const propertySignature: ts.PropertySignature = astDeclaration.declaration as ts.PropertySignature;
|
||||
|
||||
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
|
||||
|
@ -1257,6 +1337,13 @@ export class ApiModelGenerator {
|
|||
fileLine: jsDoc && 'meta' in jsDoc ? jsDoc.meta.line : sourceLocation.sourceFileLine,
|
||||
fileColumn: sourceLocation.sourceFileColumn,
|
||||
});
|
||||
} else if (parentApiItem.kind === ApiItemKind.Class || parentApiItem.kind === ApiItemKind.Interface) {
|
||||
apiPropertySignature = new ApiPropertySignature(
|
||||
this._mapProp(jsDoc as DocgenPropertyJson, parentApiItem.getAssociatedPackage()!.name),
|
||||
);
|
||||
} else {
|
||||
console.log(`We got a property in ApiItem of kind ${ApiItemKind[parentApiItem.kind]}`);
|
||||
}
|
||||
|
||||
parentApiItem.addMember(apiPropertySignature);
|
||||
} else {
|
||||
|
@ -1582,9 +1669,11 @@ export class ApiModelGenerator {
|
|||
(arr, [type, symbol]) => [
|
||||
...arr,
|
||||
{
|
||||
kind: ExcerptTokenKind.Reference,
|
||||
kind: type?.includes("'") ? ExcerptTokenKind.Content : ExcerptTokenKind.Reference,
|
||||
text: type ?? 'unknown',
|
||||
canonicalReference: DeclarationReference.package(this._apiModel.packages[0]!.name)
|
||||
canonicalReference: type?.includes("'")
|
||||
? undefined
|
||||
: DeclarationReference.package(this._apiModel.packages[0]!.name)
|
||||
.addNavigationStep(Navigation.Members as any, DeclarationReference.parseComponent(type ?? 'unknown'))
|
||||
.withMeaning(
|
||||
lookup[
|
||||
|
@ -1608,4 +1697,130 @@ export class ApiModelGenerator {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
private _mapProp(prop: DocgenPropertyJson, _package: string): IApiPropertyOptions {
|
||||
const mappedVarType = this._mapVarType(prop.type);
|
||||
return {
|
||||
name: prop.name,
|
||||
isAbstract: Boolean(prop.abstract),
|
||||
isProtected: prop.access === 'protected',
|
||||
isStatic: prop.scope === 'static',
|
||||
isOptional: Boolean(prop.nullable),
|
||||
isReadonly: Boolean(prop.readonly),
|
||||
docComment: this._tsDocParser.parseString(
|
||||
`/**\n * ${prop.description}\n${prop.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''}${
|
||||
prop.readonly ? ' * @readonly\n' : ''
|
||||
} */`,
|
||||
).docComment,
|
||||
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 },
|
||||
releaseTag: prop.access === 'public' ? ReleaseTag.Public : ReleaseTag.Internal,
|
||||
fileLine: prop.meta?.line ?? 0,
|
||||
fileUrlPath: prop.meta ? `${prop.meta.path.slice(`packages/${_package}/`.length)}/${prop.meta.file}` : '',
|
||||
};
|
||||
}
|
||||
|
||||
private _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),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private _mapMethod(method: DocgenMethodJson, _package: string): IApiMethodOptions {
|
||||
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 = this._mapVarType(method.params![index]!.type);
|
||||
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 = this._mapVarType(method.params[method.params.length - 1]!.type);
|
||||
paramTokens.push(newTokens.length);
|
||||
excerptTokens.push(...newTokens);
|
||||
excerptTokens.push({ kind: ExcerptTokenKind.Content, text: `): ` });
|
||||
}
|
||||
|
||||
const returnTokens = this._mapVarType(method.returns?.[0] ?? []);
|
||||
excerptTokens.push(...returnTokens);
|
||||
|
||||
excerptTokens.push({ kind: ExcerptTokenKind.Content, text: ';' });
|
||||
|
||||
return {
|
||||
name: method.name,
|
||||
isAbstract: Boolean(method.abstract),
|
||||
isOptional: false,
|
||||
isProtected: method.access === 'protected',
|
||||
isStatic: method.scope === 'static',
|
||||
overloadIndex: 1,
|
||||
parameters: method.params?.map((param, index) => this._mapParam(param, index, _package, paramTokens)) ?? [],
|
||||
releaseTag: method.access === 'public' ? ReleaseTag.Public : ReleaseTag.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: this._tsDocParser.parseString(
|
||||
`/**\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}`
|
||||
: ''
|
||||
}${method.examples?.map((example) => ` * @example\n * \`\`\`js\n * ${example}\n * \`\`\`\n`).join('') ?? ''}${
|
||||
method.deprecated
|
||||
? ` * @deprecated ${typeof method.deprecated === 'boolean' ? 'yes' : method.deprecated}\n`
|
||||
: ''
|
||||
} */`,
|
||||
).docComment,
|
||||
excerptTokens,
|
||||
fileLine: method.meta.line,
|
||||
fileUrlPath: `${method.meta.path.slice(`packages/${_package}/`.length)}/${method.meta.file}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue