feat: mainlib docs on new website (#9930)

* fix(ExceptText): don't display import("d..-types/v10"). in return type

* Squashed 'packages/api-extractor-model/' content from commit 39ecb196c

git-subtree-dir: packages/api-extractor-model
git-subtree-split: 39ecb196ca210bdf84ba6c9cadb1bb93571849d7

* Squashed 'packages/api-extractor/' content from commit 341ad6c51

git-subtree-dir: packages/api-extractor
git-subtree-split: 341ad6c51b01656d4f73b74ad4bdb3095f9262c4

* feat(api-extractor): add api-extractor and -model

* fix: package.json docs script

* fix(SourcLink): use <> instead of function syntax

* fix: make packages private

* fix: rest params showing in docs, added labels

* fix: missed two files

* feat: merge docs.json from docgen and docs.api.json

* fix: cpy-cli & pnpm-lock

* fix: increase icon size

* fix: icon size again

* feat: run both docs on mainlib

* chore: website fixes

* fix: more website fixes

* fix: tests and dev database script

* chore: comment out old docs

* fix: increase max fetch cache

* fix: env should always be a string

* fix: try to reapply patches

* fix: remove prepare for docgen

* fix: temporary cosmetic fixes

* fix: horizontal scroll

* feat: generate index for new docs

---------

Co-authored-by: Noel <buechler.noel@outlook.com>
This commit is contained in:
Qjuh
2023-11-08 10:16:54 +01:00
committed by GitHub
parent f713e47b0a
commit da455bceea
63 changed files with 1877 additions and 253 deletions

View File

@@ -1,4 +1,4 @@
# @microsoft/api-extractor-model
# @discordjs/api-extractor-model
Use this library to read and write \*.api.json files as defined by the [API Extractor](https://api-extractor.com/) tool.
These files are used to generate a documentation website for your TypeScript package. The files store the
@@ -60,7 +60,7 @@ a namespace containing static members of the class.
## Links
- [CHANGELOG.md](https://github.com/microsoft/rushstack/blob/main/libraries/api-extractor-model/CHANGELOG.md) - Find
- [CHANGELOG.md](https://github.com/discordjs/discord.js/blob/main/packages/api-extractor-model/CHANGELOG.md) - Find
out what's new in the latest version
- [API Reference](https://rushstack.io/pages/api/api-extractor-model/)

View File

@@ -58,12 +58,13 @@ export type { Constructor, PropertiesOf } from './mixins/Mixin.js';
// model
export { type IApiCallSignatureOptions, ApiCallSignature } from './model/ApiCallSignature.js';
export { type IApiClassOptions, ApiClass } from './model/ApiClass.js';
export { type IApiClassOptions, ApiClass, type IExcerptTokenRangeWithTypeParameters } from './model/ApiClass.js';
export { type IApiConstructorOptions, ApiConstructor } from './model/ApiConstructor.js';
export { type IApiConstructSignatureOptions, ApiConstructSignature } from './model/ApiConstructSignature.js';
export { type IApiEntryPointOptions, ApiEntryPoint } from './model/ApiEntryPoint.js';
export { type IApiEnumOptions, ApiEnum } from './model/ApiEnum.js';
export { type IApiEnumMemberOptions, ApiEnumMember, EnumMemberOrder } from './model/ApiEnumMember.js';
export { type IApiEventOptions, ApiEvent } from './model/ApiEvent.js';
export { type IApiFunctionOptions, ApiFunction } from './model/ApiFunction.js';
export { type IApiIndexSignatureOptions, ApiIndexSignature } from './model/ApiIndexSignature.js';
export { type IApiInterfaceOptions, ApiInterface } from './model/ApiInterface.js';

View File

@@ -8,6 +8,7 @@ import { ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import type { Constructor, PropertiesOf } from '../mixins/Mixin.js';
import type { ApiModel } from '../model/ApiModel.js';
import type { ApiPackage } from '../model/ApiPackage.js';
import type { DocgenJson } from '../model/Deserializer';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
@@ -24,6 +25,7 @@ export enum ApiItemKind {
EntryPoint = 'EntryPoint',
Enum = 'Enum',
EnumMember = 'EnumMember',
Event = 'Event',
Function = 'Function',
IndexSignature = 'IndexSignature',
Interface = 'Interface',
@@ -111,6 +113,12 @@ export class ApiItem {
return deserializerModule.Deserializer.deserialize(context, jsonObject);
}
public static deserializeDocgen(jsonObject: DocgenJson, _package: string): ApiItem {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const deserializerModule: typeof import('../model/Deserializer') = require('../model/Deserializer');
return deserializerModule.Deserializer.deserializeDocgen(jsonObject, _package);
}
/**
* @virtual
*/

View File

@@ -3,6 +3,7 @@
import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { InternalError } from '@rushstack/node-core-library';
import type { ApiDeclaredItem } from '../index.js';
import {
ApiItem,
apiItem_onParentChanged,
@@ -36,6 +37,11 @@ export interface IApiItemContainerJson extends IApiItemJson {
preserveMemberOrder?: boolean;
}
interface IMappedTypeParameters {
item: ApiItem;
mappedTypeParameters: Map<string, string>;
}
const _members: unique symbol = Symbol('ApiItemContainerMixin._members');
const _membersSorted: unique symbol = Symbol('ApiItemContainerMixin._membersSorted');
const _membersByContainerKey: unique symbol = Symbol('ApiItemContainerMixin._membersByContainerKey');
@@ -299,23 +305,76 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
}
}
// The Deserializer class is coupled with a ton of other classes, so we delay loading it
// to avoid ES5 circular imports.
// *eslint-disable-next-line @typescript-eslint/consistent-type-imports, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
// const deserializerModule: typeof import('../model/Deserializer') = require('../model/Deserializer');
const membersByName: Map<string, ApiItem[]> = new Map();
const membersByKind: Map<ApiItemKind, ApiItem[]> = new Map();
const toVisit: ApiItem[] = [];
let next: ApiItem | undefined = this;
const toVisit: IMappedTypeParameters[] = [];
let next: IMappedTypeParameters | undefined = { item: this, mappedTypeParameters: new Map() };
while (next) {
const membersToAdd: ApiItem[] = [];
while (next?.item) {
const membersToAdd: ApiItem[] = []; /*
const typeParams = next.mappedTypeParameters;
const context: DeserializerContext = {
apiJsonFilename: '',
toolPackage: '',
toolVersion: '',
versionToDeserialize: ApiJsonSchemaVersion.LATEST,
tsdocConfiguration: new TSDocConfiguration(),
}; */
// For each member, check to see if we've already seen a member with the same name
// previously in the inheritance tree. If so, we know we won't inherit it, and thus
// do not add it to our `membersToAdd` array.
for (const member of next.members) {
for (const member of next.item.members) {
// We add the to-be-added members to an intermediate array instead of immediately
// to the maps themselves to support method overloads with the same name.
if (ApiNameMixin.isBaseClassOf(member)) {
if (!membersByName.has(member.name)) {
// This was supposed to replace type parameters with their assigned values in inheritance, but doesn't work yet
/*
if (
ApiTypeParameterListMixin.isBaseClassOf(member) &&
member.typeParameters.some((param) => typeParams.has(param.name))
) {
const jsonObject: Partial<IApiItemJson> = {};
member.serializeInto(jsonObject);
member = deserializerModule.Deserializer.deserialize(context, {
...jsonObject,
typeParameters: (jsonObject as IApiTypeParameterListMixinJson).typeParameters.map(
({ typeParameterName, constraintTokenRange, defaultTypeTokenRange }) => ({
typeParameterName: typeParams.get(typeParameterName) ?? typeParameterName,
defaultTypeTokenRange,
constraintTokenRange,
}),
),
} as IApiTypeParameterListMixinJson);
}
if (ApiReturnTypeMixin.isBaseClassOf(member)) {
const jsonObject: Partial<IApiItemJson> = {};
member.serializeInto(jsonObject);
member = deserializerModule.Deserializer.deserialize(context, {
...(jsonObject as IApiReturnTypeMixinJson),
excerptTokens: (jsonObject as IApiDeclaredItemJson).excerptTokens.map((token) =>
token.kind === ExcerptTokenKind.Content
? {
kind: ExcerptTokenKind.Content,
text: [...typeParams.keys()].reduce(
(tok, typ) => tok.replaceAll(new RegExp(`\b${typ}\b`, 'g'), typeParams.get(typ)!),
token.text,
),
}
: token,
),
} as IApiReturnTypeMixinJson);
member[apiItem_onParentChanged](next.item);
} // */
membersToAdd.push(member);
}
} else if (!membersByKind.has(member.kind)) {
@@ -336,18 +395,18 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
}
// Interfaces can extend multiple interfaces, so iterate through all of them.
const extendedItems: ApiItem[] = [];
const extendedItems: IMappedTypeParameters[] = [];
let extendsTypes: readonly HeritageType[] | undefined;
switch (next.kind) {
switch (next.item.kind) {
case ApiItemKind.Class: {
const apiClass: ApiClass = next as ApiClass;
const apiClass: ApiClass = next.item as ApiClass;
extendsTypes = apiClass.extendsType ? [apiClass.extendsType] : [];
break;
}
case ApiItemKind.Interface: {
const apiInterface: ApiInterface = next as ApiInterface;
const apiInterface: ApiInterface = next.item as ApiInterface;
extendsTypes = apiInterface.extendsTypes;
break;
}
@@ -359,7 +418,7 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
if (extendsTypes === undefined) {
messages.push({
messageId: FindApiItemsMessageId.UnsupportedKind,
text: `Unable to analyze references of API item ${next.displayName} because it is of unsupported kind ${next.kind}`,
text: `Unable to analyze references of API item ${next.item.displayName} because it is of unsupported kind ${next.item.kind}`,
});
maybeIncompleteResult = true;
next = toVisit.shift();
@@ -387,7 +446,7 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
if (!firstReferenceToken) {
messages.push({
messageId: FindApiItemsMessageId.ExtendsClauseMissingReference,
text: `Unable to analyze extends clause ${extendsType.excerpt.text} of API item ${next.displayName} because no canonical reference was found`,
text: `Unable to analyze extends clause ${extendsType.excerpt.text} of API item ${next.item.displayName} because no canonical reference was found`,
});
maybeIncompleteResult = true;
continue;
@@ -397,7 +456,7 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
if (!apiModel) {
messages.push({
messageId: FindApiItemsMessageId.NoAssociatedApiModel,
text: `Unable to analyze references of API item ${next.displayName} because it is not associated with an ApiModel`,
text: `Unable to analyze references of API item ${next.item.displayName} because it is not associated with an ApiModel`,
});
maybeIncompleteResult = true;
continue;
@@ -413,13 +472,24 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
if (!apiItem) {
messages.push({
messageId: FindApiItemsMessageId.DeclarationResolutionFailed,
text: `Unable to resolve declaration reference within API item ${next.displayName}: ${apiItemResult.errorMessage}`,
text: `Unable to resolve declaration reference within API item ${next.item.displayName}: ${apiItemResult.errorMessage}`,
});
maybeIncompleteResult = true;
continue;
}
extendedItems.push(apiItem);
const mappedTypeParameters: Map<string, string> = new Map();
if (
(apiItem.kind === ApiItemKind.Class || apiItem.kind === ApiItemKind.Interface) &&
next.item.kind === ApiItemKind.Class
) {
for (const [index, typeParameter] of extendsType.typeParameters.entries()) {
const key = (apiItem as ApiClass | ApiInterface).typeParameters[index]?.name ?? '';
mappedTypeParameters.set(key, typeParameter);
}
}
extendedItems.push({ item: apiItem, mappedTypeParameters });
}
// For classes, this array will only have one item. For interfaces, there may be multiple items. Sort the array
@@ -440,7 +510,9 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
//
// interface FooBar extends Foo, Bar {}
// ```
extendedItems.sort((x: ApiItem, y: ApiItem) => x.getSortKey().localeCompare(y.getSortKey()));
extendedItems.sort((x: IMappedTypeParameters, y: IMappedTypeParameters) =>
x.item.getSortKey().localeCompare(y.item.getSortKey()),
);
toVisit.push(...extendedItems);
next = toVisit.shift();

View File

@@ -147,7 +147,11 @@ export class Excerpt {
this.tokenRange.endIndex > this.tokens.length ||
this.tokenRange.startIndex > this.tokenRange.endIndex
) {
throw new Error('Invalid token range');
throw new Error(
`Invalid token range. length:${this.tokens.length}, start:${this.tokenRange.startIndex}, end:${
this.tokenRange.endIndex
}, ${this.tokens.map((token) => token.text)}`,
);
}
this.spannedTokens = this.tokens.slice(this.tokenRange.startIndex, this.tokenRange.endIndex);

View File

@@ -39,8 +39,12 @@ export interface IApiClassOptions
IApiDeclaredItemOptions,
IApiTypeParameterListMixinOptions,
IApiExportedMixinOptions {
extendsTokenRange: IExcerptTokenRange | undefined;
implementsTokenRanges: IExcerptTokenRange[];
extendsTokenRange: IExcerptTokenRangeWithTypeParameters | undefined;
implementsTokenRanges: IExcerptTokenRangeWithTypeParameters[];
}
export interface IExcerptTokenRangeWithTypeParameters extends IExcerptTokenRange {
typeParameters: string[];
}
export interface IApiClassJson
@@ -48,8 +52,8 @@ export interface IApiClassJson
IApiAbstractMixinJson,
IApiTypeParameterListMixinJson,
IApiExportedMixinJson {
extendsTokenRange?: IExcerptTokenRange;
implementsTokenRanges: IExcerptTokenRange[];
extendsTokenRange?: IExcerptTokenRangeWithTypeParameters | undefined;
implementsTokenRanges: IExcerptTokenRangeWithTypeParameters[];
}
/**
@@ -81,13 +85,18 @@ export class ApiClass extends ApiItemContainerMixin(
super(options);
if (options.extendsTokenRange) {
this.extendsType = new HeritageType(this.buildExcerpt(options.extendsTokenRange));
this.extendsType = new HeritageType(
this.buildExcerpt(options.extendsTokenRange),
options.extendsTokenRange.typeParameters,
);
} else {
this.extendsType = undefined;
}
for (const implementsTokenRange of options.implementsTokenRanges) {
this._implementsTypes.push(new HeritageType(this.buildExcerpt(implementsTokenRange)));
this._implementsTypes.push(
new HeritageType(this.buildExcerpt(implementsTokenRange), implementsTokenRange.typeParameters),
);
}
}
@@ -138,10 +147,16 @@ export class ApiClass extends ApiItemContainerMixin(
// Note that JSON does not support the "undefined" value, so we simply omit the field entirely if it is undefined
if (this.extendsType) {
jsonObject.extendsTokenRange = this.extendsType.excerpt.tokenRange;
jsonObject.extendsTokenRange = {
...this.extendsType.excerpt.tokenRange,
typeParameters: this.extendsType.typeParameters,
};
}
jsonObject.implementsTokenRanges = this.implementsTypes.map((x) => x.excerpt.tokenRange);
jsonObject.implementsTokenRanges = this.implementsTypes.map((x) => ({
...x.excerpt.tokenRange,
typeParameters: x.typeParameters,
}));
}
/**

View File

@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { type IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin.js';
/**
* Constructor options for {@link ApiEvent}.
*
* @public
*/
export interface IApiEventOptions
extends IApiNameMixinOptions,
IApiParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions {}
/**
* Represents a TypeScript event declaration that belongs to an `ApiClass`.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiEvent` represents a emittable event such as the `ready` event in this example:
*
* ```ts
* export class Cliet extends EventEmitter {
* on(event: 'ready', ...args: [Client]): this { }
* }
* ```
* @public
*/
export class ApiEvent extends ApiNameMixin(ApiParameterListMixin(ApiReleaseTagMixin(ApiDeclaredItem))) {
public constructor(options: IApiEventOptions) {
super(options);
}
public static getContainerKey(name: string, overloadIndex: number): string {
return `${name}|${ApiItemKind.Event}|${overloadIndex}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Event;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiEvent.getContainerKey(this.name, this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(Navigation.Members as any, nameComponent)
.withMeaning(Meaning.Member as any)
.withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -25,7 +25,7 @@ import {
type IApiTypeParameterListMixinJson,
ApiTypeParameterListMixin,
} from '../mixins/ApiTypeParameterListMixin.js';
import type { IExcerptTokenRange } from '../mixins/Excerpt.js';
import type { IExcerptTokenRangeWithTypeParameters } from './ApiClass.js';
import type { DeserializerContext } from './DeserializerContext.js';
import { HeritageType } from './HeritageType.js';
@@ -41,7 +41,7 @@ export interface IApiInterfaceOptions
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions,
IApiExportedMixinOptions {
extendsTokenRanges: IExcerptTokenRange[];
extendsTokenRanges: IExcerptTokenRangeWithTypeParameters[];
}
export interface IApiInterfaceJson
@@ -51,7 +51,7 @@ export interface IApiInterfaceJson
IApiReleaseTagMixinJson,
IApiDeclaredItemJson,
IApiExportedMixinJson {
extendsTokenRanges: IExcerptTokenRange[];
extendsTokenRanges: IExcerptTokenRangeWithTypeParameters[];
}
/**
@@ -79,7 +79,7 @@ export class ApiInterface extends ApiItemContainerMixin(
super(options);
for (const extendsTokenRange of options.extendsTokenRanges) {
this._extendsTypes.push(new HeritageType(this.buildExcerpt(extendsTokenRange)));
this._extendsTypes.push(new HeritageType(this.buildExcerpt(extendsTokenRange), extendsTokenRange.typeParameters));
}
}
@@ -127,7 +127,10 @@ export class ApiInterface extends ApiItemContainerMixin(
public override serializeInto(jsonObject: Partial<IApiInterfaceJson>): void {
super.serializeInto(jsonObject);
jsonObject.extendsTokenRanges = this.extendsTypes.map((x) => x.excerpt.tokenRange);
jsonObject.extendsTokenRanges = this.extendsTypes.map((x) => ({
...x.excerpt.tokenRange,
typeParameters: x.typeParameters,
}));
}
/**

View File

@@ -1,9 +1,24 @@
// 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';
@@ -11,6 +26,8 @@ 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';
@@ -23,7 +40,313 @@ 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 type { DeserializerContext } from './DeserializerContext.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<IExcerptToken[]>(
(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),
},
};
}
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 {
@@ -51,6 +374,9 @@ export class Deserializer {
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);
@@ -90,4 +416,162 @@ export class Deserializer {
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);
}
}

View File

@@ -38,7 +38,10 @@ export class HeritageType {
*/
public readonly excerpt: Excerpt;
public constructor(excerpt: Excerpt) {
public readonly typeParameters: string[];
public constructor(excerpt: Excerpt, typeParameters: string[]) {
this.excerpt = excerpt;
this.typeParameters = typeParameters;
}
}

View File

@@ -1,4 +1,4 @@
# @microsoft/api-extractor
# @discordjs/api-extractor
![API Extractor](https://github.com/microsoft/rushstack/raw/main/common/wiki-images/api-extractor-title.png?raw=true)
<br />
@@ -42,7 +42,7 @@ For more details and support resources, please visit: https://api-extractor.com/
## Links
- [CHANGELOG.md](https://github.com/microsoft/rushstack/blob/main/apps/api-extractor/CHANGELOG.md) - Find
- [CHANGELOG.md](https://github.com/discordjs/discord.js/blob/main/packages/api-extractor/CHANGELOG.md) - Find
out what's new in the latest version
- [API Reference](https://rushstack.io/pages/api/api-extractor/)

View File

@@ -7,7 +7,7 @@
* ```
* {
* "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
* "extends": [ "@microsoft/api-extractor/extends/tsdoc-config.json" ],
* "extends": [ "@discordjs/api-extractor/extends/tsdoc-config.json" ],
* . . .
* }
* ```

View File

@@ -131,7 +131,7 @@ export class PackageMetadataManager {
tsdocVersion: '0.12',
toolPackages: [
{
packageName: '@microsoft/api-extractor',
packageName: '@discordjs/api-extractor',
packageVersion: Extractor.version,
},
],

View File

@@ -121,5 +121,3 @@ describe(PackageMetadataManager.name, () => {
});
});
});
/* eslint-enable @typescript-eslint/typedef */

View File

@@ -142,7 +142,7 @@ export interface IExtractorConfigPrepareOptions {
/**
* Allow customization of the tsdoc.json config file. If omitted, this file will be loaded from its default
* location. If the file does not exist, then the standard definitions will be used from
* `@microsoft/api-extractor/extends/tsdoc-base.json`.
* `@discordjs/api-extractor/extends/tsdoc-base.json`.
*/
tsdocConfigFile?: TSDocConfigFile;
}

View File

@@ -1,12 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { existsSync } from 'node:fs';
import * as path from 'node:path';
import {
ApiModel,
ApiClass,
ApiPackage,
ApiEntryPoint,
ApiEvent,
ApiMethod,
ApiNamespace,
ApiInterface,
@@ -19,6 +21,7 @@ import {
ApiEnum,
ApiEnumMember,
type IExcerptTokenRange,
type IExcerptTokenRangeWithTypeParameters,
type IExcerptToken,
ApiConstructor,
ApiConstructSignature,
@@ -29,12 +32,17 @@ import {
ApiCallSignature,
type IApiTypeParameterOptions,
EnumMemberOrder,
ExcerptTokenKind,
Navigation,
} from '@discordjs/api-extractor-model';
import type * as tsdoc from '@microsoft/tsdoc';
import { Path } from '@rushstack/node-core-library';
import { TSDocParser } from '@microsoft/tsdoc';
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { JsonFile, Path } from '@rushstack/node-core-library';
import * as ts from 'typescript';
import type { AstDeclaration } from '../analyzer/AstDeclaration.js';
import type { AstEntity } from '../analyzer/AstEntity.js';
import type { AstImport } from '../analyzer/AstImport.js';
import type { AstModule } from '../analyzer/AstModule.js';
import { AstNamespaceImport } from '../analyzer/AstNamespaceImport.js';
import { AstSymbol } from '../analyzer/AstSymbol.js';
@@ -46,10 +54,165 @@ import type { ISourceLocation } from '../collector/SourceMapper.js';
import { DeclarationReferenceGenerator } from './DeclarationReferenceGenerator.js';
import { ExcerptBuilder, type IExcerptBuilderNodeToCapture } from './ExcerptBuilder.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;
type DocgenContainerJson =
| DocgenClassJson
| DocgenConstructorJson
| DocgenInterfaceJson
| DocgenJson
| DocgenMethodJson
| DocgenTypedefJson;
export interface DocgenJson {
classes: DocgenClassJson[];
externals: DocgenExternalJson[];
functions: DocgenMethodJson[];
interfaces: DocgenInterfaceJson[];
meta: {
date: number;
format: number;
generator: string;
};
typedefs: DocgenTypedefJson[];
}
interface IProcessAstEntityContext {
isExported: boolean;
name: string;
parentApiItem: ApiItemContainerMixin;
parentDocgenJson?: DocgenContainerJson | undefined;
}
const linkRegEx = /{@link\s(?<class>\w+)#(?<event>event:)?(?<prop>[\w()]+)(?<name>\s[^}]*)?}/g;
function fixLinkTags(input?: string): string | undefined {
return input?.replaceAll(linkRegEx, '{@link $<class>.$<prop>$<name>}');
}
function filePathFromJson(meta: DocgenMetaJson): string {
return `${meta.path.slice('packages/discord.js/'.length)}/${meta.file}`;
}
export class ApiModelGenerator {
@@ -71,6 +234,12 @@ export class ApiModelGenerator {
public buildApiPackage(): ApiPackage {
const packageDocComment: tsdoc.DocComment | undefined = this._collector.workingPackage.tsdocComment;
let jsDocJson: DocgenJson | undefined;
const jsDocFilepath = `${this._collector.extractorConfig.apiJsonFilePath.slice(0, -8)}json`;
if (existsSync(jsDocFilepath)) {
jsDocJson = JsonFile.load(jsDocFilepath);
}
const apiPackage: ApiPackage = new ApiPackage({
name: this._collector.workingPackage.name,
@@ -92,6 +261,7 @@ export class ApiModelGenerator {
name: entity.nameForEmit!,
isExported: entity.exportedFromEntryPoint,
parentApiItem: apiEntryPoint,
parentDocgenJson: jsDocJson,
});
}
}
@@ -320,6 +490,7 @@ export class ApiModelGenerator {
const constructorDeclaration: ts.ConstructorDeclaration = astDeclaration.declaration as ts.ConstructorDeclaration;
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined;
const parameters: IApiParameterOptions[] = this._captureParameters(
nodesToCapture,
@@ -328,7 +499,15 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = parent?.construct
? new TSDocParser().parseString(
`/*+\n * ${fixLinkTags(parent.construct.description)}\n${
parent.construct.params
?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`)
.join('') ?? ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const isProtected: boolean = (astDeclaration.modifierFlags & ts.ModifierFlags.Protected) !== 0;
const sourceLocation: ISourceLocation = this._getSourceLocation(constructorDeclaration);
@@ -340,8 +519,8 @@ export class ApiModelGenerator {
parameters,
overloadIndex,
excerptTokens,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: parent ? filePathFromJson(parent.meta) : sourceLocation.sourceFilePath,
fileLine: parent?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -354,9 +533,14 @@ export class ApiModelGenerator {
const containerKey: string = ApiClass.getContainerKey(name);
let apiClass: ApiClass | undefined = parentApiItem.tryGetMemberByKey(containerKey) as ApiClass;
const parent = context.parentDocgenJson as DocgenJson | undefined;
const jsDoc = parent?.classes.find((_class) => _class.name === name);
if (apiClass === undefined) {
const classDeclaration: ts.ClassDeclaration = astDeclaration.declaration as ts.ClassDeclaration;
if (name === 'ActionRow') {
console.dir(classDeclaration.heritageClauses?.[0]?.types[0]?.typeArguments, { depth: 3 });
}
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
@@ -365,18 +549,29 @@ export class ApiModelGenerator {
classDeclaration.typeParameters,
);
let extendsTokenRange: IExcerptTokenRange | undefined;
const implementsTokenRanges: IExcerptTokenRange[] = [];
let extendsTokenRange: IExcerptTokenRangeWithTypeParameters | undefined;
const implementsTokenRanges: IExcerptTokenRangeWithTypeParameters[] = [];
for (const heritageClause of classDeclaration.heritageClauses ?? []) {
if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
extendsTokenRange = ExcerptBuilder.createEmptyTokenRange();
extendsTokenRange = ExcerptBuilder.createEmptyTokenRangeWithTypeParameters();
if (heritageClause.types.length > 0) {
extendsTokenRange.typeParameters.push(
...(heritageClause.types[0]?.typeArguments?.map((typeArgument) =>
ts.isTypeReferenceNode(typeArgument) ? typeArgument.typeName.getText() : '',
) ?? []),
);
nodesToCapture.push({ node: heritageClause.types[0], tokenRange: extendsTokenRange });
}
} else if (heritageClause.token === ts.SyntaxKind.ImplementsKeyword) {
for (const heritageType of heritageClause.types) {
const implementsTokenRange: IExcerptTokenRange = ExcerptBuilder.createEmptyTokenRange();
const implementsTokenRange: IExcerptTokenRangeWithTypeParameters =
ExcerptBuilder.createEmptyTokenRangeWithTypeParameters();
implementsTokenRange.typeParameters.push(
...(heritageClause.types[0]?.typeArguments?.map((typeArgument) =>
ts.isTypeReferenceNode(typeArgument) ? typeArgument.typeName.getText() : '',
) ?? []),
);
implementsTokenRanges.push(implementsTokenRange);
nodesToCapture.push({ node: heritageType, tokenRange: implementsTokenRange });
}
@@ -385,7 +580,17 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${jsDoc.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''}${
jsDoc.deprecated
? ` * @deprecated ${
typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated
}\n`
: ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const isAbstract: boolean = (ts.getCombinedModifierFlags(classDeclaration) & ts.ModifierFlags.Abstract) !== 0;
const sourceLocation: ISourceLocation = this._getSourceLocation(classDeclaration);
@@ -400,8 +605,8 @@ export class ApiModelGenerator {
extendsTokenRange,
implementsTokenRanges,
isExported,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -411,7 +616,12 @@ export class ApiModelGenerator {
this._processChildDeclarations(astDeclaration, {
...context,
parentApiItem: apiClass,
parentDocgenJson: jsDoc,
});
for (const event of jsDoc?.events ?? []) {
this._processApiEvent({ ...context, name: event.name, parentApiItem: apiClass, parentDocgenJson: jsDoc });
}
}
private _processApiConstructSignature(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
@@ -428,6 +638,7 @@ export class ApiModelGenerator {
astDeclaration.declaration as ts.ConstructSignatureDeclaration;
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined;
const returnTypeTokenRange: IExcerptTokenRange = ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: constructSignature.type, tokenRange: returnTypeTokenRange });
@@ -441,7 +652,15 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = parent?.construct
? new TSDocParser().parseString(
`/*+\n * ${fixLinkTags(parent.construct.description)}\n${
parent.construct.params
?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`)
.join('') ?? ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const sourceLocation: ISourceLocation = this._getSourceLocation(constructSignature);
@@ -453,8 +672,8 @@ export class ApiModelGenerator {
overloadIndex,
excerptTokens,
returnTypeTokenRange,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: parent ? filePathFromJson(parent.meta) : sourceLocation.sourceFilePath,
fileLine: parent?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -541,6 +760,8 @@ export class ApiModelGenerator {
const containerKey: string = ApiFunction.getContainerKey(name, overloadIndex);
let apiFunction: ApiFunction | undefined = parentApiItem.tryGetMemberByKey(containerKey) as ApiFunction;
const parent = context.parentDocgenJson as DocgenJson | undefined;
const jsDoc = parent?.functions.find((fun) => fun.name === name);
if (apiFunction === undefined) {
const functionDeclaration: ts.FunctionDeclaration = astDeclaration.declaration as ts.FunctionDeclaration;
@@ -562,7 +783,24 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
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.deprecated
? ` * @deprecated ${
typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated
}\n`
: ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const sourceLocation: ISourceLocation = this._getSourceLocation(functionDeclaration);
@@ -576,8 +814,8 @@ export class ApiModelGenerator {
excerptTokens,
returnTypeTokenRange,
isExported,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -633,6 +871,9 @@ export class ApiModelGenerator {
const containerKey: string = ApiInterface.getContainerKey(name);
let apiInterface: ApiInterface | undefined = parentApiItem.tryGetMemberByKey(containerKey) as ApiInterface;
const parent = context.parentDocgenJson as DocgenJson | undefined;
const jsDoc =
parent?.interfaces.find((int) => int.name === name) ?? parent?.typedefs.find((int) => int.name === name);
if (apiInterface === undefined) {
const interfaceDeclaration: ts.InterfaceDeclaration = astDeclaration.declaration as ts.InterfaceDeclaration;
@@ -644,12 +885,18 @@ export class ApiModelGenerator {
interfaceDeclaration.typeParameters,
);
const extendsTokenRanges: IExcerptTokenRange[] = [];
const extendsTokenRanges: IExcerptTokenRangeWithTypeParameters[] = [];
for (const heritageClause of interfaceDeclaration.heritageClauses ?? []) {
if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
for (const heritageType of heritageClause.types) {
const extendsTokenRange: IExcerptTokenRange = ExcerptBuilder.createEmptyTokenRange();
const extendsTokenRange: IExcerptTokenRangeWithTypeParameters =
ExcerptBuilder.createEmptyTokenRangeWithTypeParameters();
extendsTokenRange.typeParameters.push(
...(heritageClause.types[0]?.typeArguments?.map((typeArgument) =>
ts.isTypeReferenceNode(typeArgument) ? typeArgument.typeName.getText() : '',
) ?? []),
);
extendsTokenRanges.push(extendsTokenRange);
nodesToCapture.push({ node: heritageType, tokenRange: extendsTokenRange });
}
@@ -658,7 +905,17 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${jsDoc.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''}${
jsDoc.deprecated
? ` * @deprecated ${
typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated
}\n`
: ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const sourceLocation: ISourceLocation = this._getSourceLocation(interfaceDeclaration);
@@ -670,8 +927,8 @@ export class ApiModelGenerator {
typeParameters,
extendsTokenRanges,
isExported,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -681,6 +938,7 @@ export class ApiModelGenerator {
this._processChildDeclarations(astDeclaration, {
...context,
parentApiItem: apiInterface,
parentDocgenJson: jsDoc,
});
}
@@ -691,6 +949,8 @@ export class ApiModelGenerator {
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) {
const methodDeclaration: ts.MethodDeclaration = astDeclaration.declaration as ts.MethodDeclaration;
@@ -709,7 +969,24 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
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.deprecated
? ` * @deprecated ${
typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated
}\n`
: ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
if (releaseTag === ReleaseTag.Internal || releaseTag === ReleaseTag.Alpha) {
return; // trim out items marked as "@internal" or "@alpha"
@@ -733,8 +1010,8 @@ export class ApiModelGenerator {
overloadIndex,
excerptTokens,
returnTypeTokenRange,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -750,6 +1027,8 @@ export class ApiModelGenerator {
let apiMethodSignature: ApiMethodSignature | undefined = parentApiItem.tryGetMemberByKey(
containerKey,
) as ApiMethodSignature;
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined;
const jsDoc = parent?.methods?.find((method) => method.name === name);
if (apiMethodSignature === undefined) {
const methodSignature: ts.MethodSignature = astDeclaration.declaration as ts.MethodSignature;
@@ -768,7 +1047,24 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
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.deprecated
? ` * @deprecated ${
typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated
}\n`
: ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const isOptional: boolean = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0;
const sourceLocation: ISourceLocation = this._getSourceLocation(methodSignature);
@@ -783,8 +1079,8 @@ export class ApiModelGenerator {
overloadIndex,
excerptTokens,
returnTypeTokenRange,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -830,6 +1126,8 @@ export class ApiModelGenerator {
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) {
const declaration: ts.Declaration = astDeclaration.declaration;
@@ -857,7 +1155,19 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''
}${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${
'deprecated' in jsDoc && jsDoc.deprecated
? ` * @deprecated ${
typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated
}\n`
: ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const isOptional: boolean = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0;
const isProtected: boolean = (astDeclaration.modifierFlags & ts.ModifierFlags.Protected) !== 0;
@@ -877,8 +1187,8 @@ export class ApiModelGenerator {
excerptTokens,
propertyTypeTokenRange,
initializerTokenRange,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc && 'meta' in jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc && 'meta' in jsDoc ? jsDoc.meta.line : sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
parentApiItem.addMember(apiProperty);
@@ -895,6 +1205,8 @@ export class ApiModelGenerator {
let apiPropertySignature: ApiPropertySignature | undefined = parentApiItem.tryGetMemberByKey(
containerKey,
) as ApiPropertySignature;
const parent = context.parentDocgenJson as DocgenInterfaceJson | DocgenPropertyJson | DocgenTypedefJson | undefined;
const jsDoc = parent?.props?.find((prop) => prop.name === name);
if (apiPropertySignature === undefined) {
const propertySignature: ts.PropertySignature = astDeclaration.declaration as ts.PropertySignature;
@@ -906,7 +1218,15 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''
}${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${
'deprecated' in jsDoc && jsDoc.deprecated ? ` * @deprecated ${jsDoc.deprecated}\n` : ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const isOptional: boolean = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0;
const isReadonly: boolean = this._isReadonly(astDeclaration);
@@ -920,8 +1240,8 @@ export class ApiModelGenerator {
excerptTokens,
propertyTypeTokenRange,
isReadonly,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc && 'meta' in jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc && 'meta' in jsDoc ? jsDoc.meta.line : sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -938,6 +1258,11 @@ export class ApiModelGenerator {
const containerKey: string = ApiTypeAlias.getContainerKey(name);
let apiTypeAlias: ApiTypeAlias | undefined = parentApiItem.tryGetMemberByKey(containerKey) as ApiTypeAlias;
const parent = context.parentDocgenJson as DocgenJson | undefined;
const jsDoc =
parent?.typedefs.find((type) => type.name === name) ??
parent?.functions.find((func) => func.name === name) ??
parent?.interfaces.find((clas) => clas.name === name);
if (apiTypeAlias === undefined) {
const typeAliasDeclaration: ts.TypeAliasDeclaration = astDeclaration.declaration as ts.TypeAliasDeclaration;
@@ -954,7 +1279,19 @@ export class ApiModelGenerator {
const excerptTokens: IExcerptToken[] = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment;
const docComment: tsdoc.DocComment | undefined = jsDoc
? new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description) ?? ''}\n${
'params' in jsDoc
? jsDoc.params.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`).join('')
: ''
}${
'returns' in jsDoc
? jsDoc.returns.map((ret) => ` * @returns ${Array.isArray(ret) ? '' : fixLinkTags(ret.description)}\n`)
: ''
} */`,
).docComment
: apiItemMetadata.tsdocComment;
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
const sourceLocation: ISourceLocation = this._getSourceLocation(typeAliasDeclaration);
@@ -966,8 +1303,8 @@ export class ApiModelGenerator {
excerptTokens,
typeTokenRange,
isExported,
fileUrlPath: sourceLocation.sourceFilePath,
fileLine: sourceLocation.sourceFileLine,
fileUrlPath: jsDoc ? filePathFromJson(jsDoc.meta) : sourceLocation.sourceFilePath,
fileLine: jsDoc?.meta.line ?? sourceLocation.sourceFileLine,
fileColumn: sourceLocation.sourceFileColumn,
});
@@ -1021,6 +1358,94 @@ export class ApiModelGenerator {
}
}
// events aren't part of typescript, we only get them from docgen JSON here
private _processApiEvent(context: IProcessAstEntityContext): void {
const { name, parentApiItem } = context;
const containerKey: string = ApiProperty.getContainerKey(name, false);
let apiEvent: ApiEvent | undefined = parentApiItem.tryGetMemberByKey(containerKey) as ApiEvent;
const parent = context.parentDocgenJson as DocgenClassJson | DocgenInterfaceJson | undefined;
const jsDoc = parent?.events?.find((prop) => prop.name === name);
if (apiEvent === undefined && jsDoc) {
const excerptTokens: IExcerptToken[] = [
{
kind: ExcerptTokenKind.Content,
text: `on('${name}', (${
jsDoc.params?.length ? `${jsDoc.params[0]?.name}${jsDoc.params[0]?.nullable ? '?' : ''}: ` : ') => {})'
}`,
},
];
const parameters: IApiParameterOptions[] = [];
for (let index = 0; index < (jsDoc.params?.length ?? 0) - 1; index++) {
const parameter = jsDoc.params![index]!;
const newTokens = this._mapVarType(parameter.type);
parameters.push({
parameterName: parameter.name,
parameterTypeTokenRange: {
startIndex: excerptTokens.length,
endIndex: excerptTokens.length + newTokens.length,
},
isOptional: Boolean(parameter.optional),
isRest: parameter.name.startsWith('...'),
});
excerptTokens.push(...newTokens);
excerptTokens.push({
kind: ExcerptTokenKind.Content,
text: `, ${jsDoc.params![index + 1]?.name}${jsDoc.params![index + 1]!.optional ? '?' : ''}: `,
});
}
if (jsDoc.params?.length) {
const parameter = jsDoc.params![jsDoc.params.length - 1]!;
const newTokens = this._mapVarType(parameter.type);
parameters.push({
parameterName: parameter.name,
parameterTypeTokenRange: {
startIndex: excerptTokens.length,
endIndex: excerptTokens.length + newTokens.length,
},
isOptional: Boolean(parameter.optional),
isRest: parameter.name.startsWith('...'),
});
excerptTokens.push(...newTokens);
excerptTokens.push({
kind: ExcerptTokenKind.Content,
text: `) => {})`,
});
}
const docComment: tsdoc.DocComment | undefined = new TSDocParser().parseString(
`/**\n * ${fixLinkTags(jsDoc.description)}\n${
jsDoc.params?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`).join('') ?? ''
}${'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''}${
'deprecated' in jsDoc && jsDoc.deprecated
? ` * @deprecated ${
typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated
}\n`
: ''
} */`,
).docComment;
const releaseTag: ReleaseTag = ReleaseTag.Public;
apiEvent = new ApiEvent({
name,
docComment,
releaseTag,
excerptTokens,
overloadIndex: 0,
parameters,
fileUrlPath: filePathFromJson(jsDoc.meta),
fileLine: jsDoc.meta.line,
fileColumn: 0,
});
parentApiItem.addMember(apiEvent);
} else {
// If the event was already declared before (via a merged interface declaration),
// we assume its signature is identical, because the language requires that.
}
}
/**
* @param astDeclaration - The declaration
* @param nodesToCapture - A list of child nodes whose token ranges we want to capture
@@ -1131,4 +1556,43 @@ export class ApiModelGenerator {
);
return sourceLocation;
}
private _mapVarType(typey: DocgenVarTypeJson): IExcerptToken[] {
const mapper = Array.isArray(typey) ? typey : typey.types ?? [];
const lookup: { [K in ts.SyntaxKind]?: string } = {
[ts.SyntaxKind.ClassDeclaration]: 'class',
[ts.SyntaxKind.InterfaceDeclaration]: 'interface',
[ts.SyntaxKind.TypeAliasDeclaration]: 'type',
};
return mapper.flatMap((typ) =>
typ.reduce<IExcerptToken[]>(
(arr, [type, symbol]) => [
...arr,
{
kind: ExcerptTokenKind.Reference,
text: type ?? 'unknown',
canonicalReference: DeclarationReference.package(this._apiModel.packages[0]!.name)
.addNavigationStep(Navigation.Members as any, DeclarationReference.parseComponent(type ?? 'unknown'))
.withMeaning(
lookup[
(
(this._collector.entities.find(
(entity) => entity.nameForEmit === type && 'astDeclarations' in entity.astEntity,
)?.astEntity as AstSymbol | undefined) ??
(
this._collector.entities.find(
(entity) => entity.nameForEmit === type && 'astSymbol' in entity.astEntity,
)?.astEntity as AstImport | undefined
)?.astSymbol
)?.astDeclarations[0]?.declaration.kind ?? ts.SyntaxKind.ClassDeclaration
] ?? ('class' as any),
)
.toString(),
},
{ kind: ExcerptTokenKind.Content, text: symbol ?? '' },
],
[],
),
);
}
}

View File

@@ -1,7 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { ExcerptTokenKind, type IExcerptToken, type IExcerptTokenRange } from '@discordjs/api-extractor-model';
import {
ExcerptTokenKind,
type IExcerptToken,
type IExcerptTokenRange,
type IExcerptTokenRangeWithTypeParameters,
} from '@discordjs/api-extractor-model';
import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference';
import * as ts from 'typescript';
import type { AstDeclaration } from '../analyzer/AstDeclaration.js';
@@ -126,6 +131,10 @@ export class ExcerptBuilder {
return { startIndex: 0, endIndex: 0 };
}
public static createEmptyTokenRangeWithTypeParameters(): IExcerptTokenRangeWithTypeParameters {
return { startIndex: 0, endIndex: 0, typeParameters: [] };
}
private static _buildSpan(excerptTokens: IExcerptToken[], span: Span, state: IBuildSpanState): boolean {
if (span.kind === ts.SyntaxKind.JSDocComment) {
// Discard any comments

View File

@@ -3,7 +3,7 @@
/**
* API Extractor helps with validation, documentation, and reviewing of the exported API for a TypeScript library.
* The `@microsoft/api-extractor` package provides the command-line tool. It also exposes a developer API that you
* The `@discordjs/api-extractor` package provides the command-line tool. It also exposes a developer API that you
* can use to invoke API Extractor programmatically.
*
* @packageDocumentation

View File

@@ -0,0 +1,7 @@
{
"extends": "../../api-extractor.json",
"mainEntryPointFilePath": "<projectFolder>/typings/index.d.ts",
"docModel": {
"projectFolderUrl": "https://github.com/discordjs/discord.js/tree/main/packages/discord.js"
}
}

View File

@@ -9,8 +9,9 @@
"lint": "prettier --check . && tslint typings/index.d.ts && cross-env ESLINT_USE_FLAT_CONFIG=false eslint --format=pretty src",
"format": "prettier --write . && cross-env ESLINT_USE_FLAT_CONFIG=false eslint --fix --format=pretty src",
"fmt": "pnpm run format",
"docs": "docgen -i './src/*.js' './src/**/*.js' -c ./docs/index.json -r ../../ -o ./docs/docs.json",
"docs": "docgen -i './src/*.js' './src/**/*.js' -c ./docs/index.json -r ../../ -o ./docs/docs.json && pnpm run docs:new",
"docs:test": "docgen -i './src/*.js' './src/**/*.js' -c ./docs/index.json -r ../../",
"docs:new": "api-extractor -d run --local",
"prepack": "pnpm run lint && pnpm run test",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/discord.js/*'",
"release": "cliff-jumper"
@@ -68,6 +69,7 @@
},
"devDependencies": {
"@discordjs/docgen": "workspace:^",
"@discordjs/api-extractor": "workspace:^",
"@favware/cliff-jumper": "2.2.1",
"@types/node": "16.18.60",
"cross-env": "^7.0.3",

View File

@@ -0,0 +1,11 @@
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
// It should be published with your NPM package. It should not be tracked by Git.
{
"tsdocVersion": "0.12",
"toolPackages": [
{
"packageName": "@discordjs/api-extractor",
"packageVersion": "7.38.1"
}
]
}

2
packages/docgen/bin/index.js Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('../dist/bin/index.js');

View File

@@ -8,6 +8,7 @@ import { build } from '../src/index.js';
export interface CLIOptions {
custom: string;
input: string[];
newOutput: string;
output: string;
root: string;
typescript: boolean;

View File

@@ -8,10 +8,9 @@
"lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src",
"format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src",
"fmt": "pnpm run format",
"prepare": "pnpm run build",
"prepack": "pnpm run format && pnpm run build"
},
"bin": "./dist/bin/index.js",
"bin": "./bin/index.js",
"exports": {
".": {
"require": {

View File

@@ -268,5 +268,185 @@ export class Documentation {
};
}
public serializeNew() {
return {
metadata: {
toolPackage: '@discordjs/docgen',
toolVersion: Documentation.FORMAT_VERSION,
schemaVersion: 1_011,
oldestForwardsCompatibleVersion: 1_001,
tsdocConfig: {
$schema: 'https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json',
noStandardTags: true,
tagDefinitions: [
{
tagName: '@alpha',
syntaxKind: 'modifier',
},
{
tagName: '@beta',
syntaxKind: 'modifier',
},
{
tagName: '@defaultValue',
syntaxKind: 'block',
},
{
tagName: '@decorator',
syntaxKind: 'block',
allowMultiple: true,
},
{
tagName: '@deprecated',
syntaxKind: 'block',
},
{
tagName: '@eventProperty',
syntaxKind: 'modifier',
},
{
tagName: '@example',
syntaxKind: 'block',
allowMultiple: true,
},
{
tagName: '@experimental',
syntaxKind: 'modifier',
},
{
tagName: '@inheritDoc',
syntaxKind: 'inline',
},
{
tagName: '@internal',
syntaxKind: 'modifier',
},
{
tagName: '@label',
syntaxKind: 'inline',
},
{
tagName: '@link',
syntaxKind: 'inline',
allowMultiple: true,
},
{
tagName: '@override',
syntaxKind: 'modifier',
},
{
tagName: '@packageDocumentation',
syntaxKind: 'modifier',
},
{
tagName: '@param',
syntaxKind: 'block',
allowMultiple: true,
},
{
tagName: '@privateRemarks',
syntaxKind: 'block',
},
{
tagName: '@public',
syntaxKind: 'modifier',
},
{
tagName: '@readonly',
syntaxKind: 'modifier',
},
{
tagName: '@remarks',
syntaxKind: 'block',
},
{
tagName: '@returns',
syntaxKind: 'block',
},
{
tagName: '@sealed',
syntaxKind: 'modifier',
},
{
tagName: '@see',
syntaxKind: 'block',
},
{
tagName: '@throws',
syntaxKind: 'block',
allowMultiple: true,
},
{
tagName: '@typeParam',
syntaxKind: 'block',
allowMultiple: true,
},
{
tagName: '@virtual',
syntaxKind: 'modifier',
},
{
tagName: '@betaDocumentation',
syntaxKind: 'modifier',
},
{
tagName: '@internalRemarks',
syntaxKind: 'block',
},
{
tagName: '@preapproved',
syntaxKind: 'modifier',
},
],
supportForTags: {
'@alpha': true,
'@beta': true,
'@defaultValue': true,
'@decorator': true,
'@deprecated': true,
'@eventProperty': true,
'@example': true,
'@experimental': true,
'@inheritDoc': true,
'@internal': true,
'@label': true,
'@link': true,
'@override': true,
'@packageDocumentation': true,
'@param': true,
'@privateRemarks': true,
'@public': true,
'@readonly': true,
'@remarks': true,
'@returns': true,
'@sealed': true,
'@see': true,
'@throws': true,
'@typeParam': true,
'@virtual': true,
'@betaDocumentation': true,
'@internalRemarks': true,
'@preapproved': true,
},
reportUnsupportedHtmlElements: false,
},
},
projectFolderUrl: 'https://github.com/discordjs/discord.js/tree/main/packages/discord.js',
kind: 'Package',
canonicalReference: 'discord.js!',
docComment: '',
name: 'discord.js',
preserveMemberOrder: false,
members: [
...[...this.classes.values()].map((_class) => _class.serialize()),
...[...this.functions.values()].map((_function) => _function.serialize()),
...[...this.interfaces.values()].map((_interface) => _interface.serialize()),
...[...this.typedefs.values()].map((_typedef) => _typedef.serialize()),
...[...this.externals.values()].map((_external) => _external.serialize()),
],
custom: this.custom,
};
}
public static readonly FORMAT_VERSION = 30;
}

View File

@@ -17,7 +17,7 @@ interface CustomFiles {
path?: string;
}
export async function build({ input, custom: customDocs, root, output, typescript }: CLIOptions) {
export async function build({ input, custom: customDocs, root, output, newOutput, typescript }: CLIOptions) {
let data: (ChildTypes & RootTypes)[] | DeclarationReflection[] = [];
if (typescript) {
console.log('Parsing Typescript in source files...');
@@ -82,5 +82,10 @@ export async function build({ input, custom: customDocs, root, output, typescrip
writeFileSync(output, JSON.stringify(docs.serialize()));
}
if (newOutput) {
console.log(`Writing to ${newOutput}...`);
writeFileSync(newOutput, JSON.stringify(docs.serializeNew()));
}
console.log('Done!');
}

View File

@@ -5,7 +5,7 @@
"description": "A set of scripts that we use for our workflows",
"private": true,
"scripts": {
"build": "tsc --noEmit && tsup",
"build": "tsc --noEmit --skipLibCheck && tsup",
"lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src turbo",
"format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src turbo",
"fmt": "pnpm run format"
@@ -53,10 +53,12 @@
},
"homepage": "https://discord.js.org",
"dependencies": {
"@discordjs/api-extractor-utils": "workspace:^",
"@actions/glob": "^0.4.0",
"@discordjs/api-extractor-model": "workspace:^",
"@discordjs/api-extractor-utils": "workspace:^",
"@microsoft/tsdoc": "0.14.2",
"@microsoft/tsdoc-config": "0.16.2",
"@planetscale/database": "^1.11.0",
"tslib": "^2.6.2",
"undici": "5.27.2",
"yaml": "2.3.4"
@@ -66,6 +68,7 @@
"@types/node": "18.18.8",
"@vitest/coverage-v8": "^0.34.6",
"cross-env": "^7.0.3",
"env-cmd": "^10.1.0",
"eslint": "^8.53.0",
"eslint-config-neon": "^0.1.57",
"eslint-formatter-pretty": "^5.0.0",

View File

@@ -1,23 +1,11 @@
import { stat, mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { cwd } from 'node:process';
import {
ApiModel,
ApiDeclaredItem,
ApiItemContainerMixin,
ApiItem,
type ApiPackage,
ApiItemKind,
} from '@discordjs/api-extractor-model';
import type { ApiPackage } from '@discordjs/api-extractor-model';
import { ApiItem, ApiModel, ApiDeclaredItem, ApiItemContainerMixin, ApiItemKind } from '@discordjs/api-extractor-model';
import { generatePath } from '@discordjs/api-extractor-utils';
import {
DocNodeKind,
type DocCodeSpan,
type DocNode,
type DocParagraph,
type DocPlainText,
TSDocConfiguration,
} from '@microsoft/tsdoc';
import { DocNodeKind, TSDocConfiguration } from '@microsoft/tsdoc';
import type { DocLinkTag, DocCodeSpan, DocNode, DocParagraph, DocPlainText } from '@microsoft/tsdoc';
import { TSDocConfigFile } from '@microsoft/tsdoc-config';
import { request } from 'undici';
@@ -29,6 +17,7 @@ export interface MemberJSON {
}
export const PACKAGES = [
'discord.js',
'brokers',
'builders',
'collection',
@@ -44,17 +33,23 @@ export const PACKAGES = [
let idx = 0;
export function addPackageToModel(model: ApiModel, data: any) {
const tsdocConfiguration = new TSDocConfiguration();
const tsdocConfigFile = TSDocConfigFile.loadFromObject(data.metadata.tsdocConfig);
tsdocConfigFile.configureParser(tsdocConfiguration);
let apiPackage: ApiPackage;
if (data.metadata) {
const tsdocConfiguration = new TSDocConfiguration();
const tsdocConfigFile = TSDocConfigFile.loadFromObject(data.metadata.tsdocConfig);
tsdocConfigFile.configureParser(tsdocConfiguration);
apiPackage = ApiItem.deserialize(data, {
apiJsonFilename: '',
toolPackage: data.metadata.toolPackage,
toolVersion: data.metadata.toolVersion,
versionToDeserialize: data.metadata.schemaVersion,
tsdocConfiguration,
}) as ApiPackage;
} else {
apiPackage = ApiItem.deserializeDocgen(data, 'discord.js') as ApiPackage;
}
const apiPackage = ApiItem.deserialize(data, {
apiJsonFilename: '',
toolPackage: data.metadata.toolPackage,
toolVersion: data.metadata.toolVersion,
versionToDeserialize: data.metadata.schemaVersion,
tsdocConfiguration,
}) as ApiPackage;
model.addMember(apiPackage);
return model;
}
@@ -82,6 +77,9 @@ export function tryResolveSummaryText(item: ApiDeclaredItem): string | null {
case DocNodeKind.PlainText:
retVal += (node as DocPlainText).text;
break;
case DocNodeKind.LinkTag:
retVal += (node as DocLinkTag).urlDestination;
break;
case DocNodeKind.Section:
case DocNodeKind.Paragraph: {
for (const child of (node as DocParagraph).nodes) {

View File

@@ -0,0 +1,32 @@
import { readFile } from 'node:fs/promises';
import process, { cwd } from 'node:process';
import { create } from '@actions/glob';
import { connect } from '@planetscale/database';
const sql = connect({
url: process.env.DATABASE_URL!,
});
process.chdir(`${cwd()}/../../`);
const globber = await create(`packages/*/docs/*.api.json`);
// const globber2 = await create(`discord.js/*.json`);
for await (const file of globber.globGenerator()) {
const parsed = /(?<semver>\d+.\d+.\d+)-?.*/.exec(file);
const data = await readFile(file, 'utf8');
if (parsed?.groups) {
console.log(parsed.groups.semver, file);
try {
await sql.execute('replace into documentation (version, data) values (?, ?)', [parsed.groups.semver, data]);
} catch (error) {
console.error(error);
}
} else {
console.log('main', file);
try {
await sql.execute('replace into documentation (version, data) values (?, ?)', ['main', data]);
} catch (error) {
console.error(error);
}
}
}

View File

@@ -1,5 +1,12 @@
import { createTsupConfig } from '../../tsup.config.js';
export default createTsupConfig({
minify: 'terser',
});
export default [
createTsupConfig({
minify: 'terser',
}),
createTsupConfig({
entry: ['src/populateDevDatabaseBranch.ts'],
format: 'esm',
minify: 'terser',
}),
];