mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-10 08:33:30 +01:00
* 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
* fix: cpy-cli & pnpm-lock
* fix: increase icon size
* fix: icon size again
208 lines
7.2 KiB
TypeScript
208 lines
7.2 KiB
TypeScript
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
// See LICENSE in the project root for license information.
|
|
|
|
import { DocDeclarationReference } from '@microsoft/tsdoc';
|
|
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
|
|
import { PackageName } from '@rushstack/node-core-library';
|
|
import { ApiItem, ApiItemKind } from '../items/ApiItem.js';
|
|
import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin.js';
|
|
import { ApiPackage } from './ApiPackage.js';
|
|
import { ModelReferenceResolver, type IResolveDeclarationReferenceResult } from './ModelReferenceResolver.js';
|
|
|
|
/**
|
|
* A serializable representation of a collection of API declarations.
|
|
*
|
|
* @remarks
|
|
*
|
|
* An `ApiModel` represents a collection of API declarations that can be serialized to disk. It captures all the
|
|
* important information needed to generate documentation, without any reliance on the TypeScript compiler engine.
|
|
*
|
|
* An `ApiModel` acts as the root of a tree of objects that all inherit from the `ApiItem` base class.
|
|
* The tree children are determined by the {@link (ApiItemContainerMixin:interface)} mixin base class. The model
|
|
* contains packages. Packages have an entry point (today, only one). And the entry point can contain various types
|
|
* of API declarations. The container relationships might look like this:
|
|
*
|
|
* ```
|
|
* Things that can contain other things:
|
|
*
|
|
* - ApiModel
|
|
* - ApiPackage
|
|
* - ApiEntryPoint
|
|
* - ApiClass
|
|
* - ApiMethod
|
|
* - ApiProperty
|
|
* - ApiEnum
|
|
* - ApiEnumMember
|
|
* - ApiInterface
|
|
* - ApiMethodSignature
|
|
* - ApiPropertySignature
|
|
* - ApiNamespace
|
|
* - (ApiClass, ApiEnum, ApiInterace, ...)
|
|
*
|
|
* ```
|
|
*
|
|
* Normally, API Extractor writes an .api.json file to disk for each project that it builds. Then, a tool like
|
|
* API Documenter can load the various `ApiPackage` objects into a single `ApiModel` and process them as a group.
|
|
* This is useful because compilation generally occurs separately (e.g. because projects may reside in different
|
|
* Git repos, or because they build with different TypeScript compiler configurations that may be incompatible),
|
|
* whereas API Documenter cannot detect broken hyperlinks without seeing the entire documentation set.
|
|
* @public
|
|
*/
|
|
export class ApiModel extends ApiItemContainerMixin(ApiItem) {
|
|
private readonly _resolver: ModelReferenceResolver;
|
|
|
|
private _packagesByName: Map<string, ApiPackage> | undefined = undefined;
|
|
|
|
private _apiItemsByCanonicalReference: Map<string, ApiItem> | undefined = undefined;
|
|
|
|
public constructor() {
|
|
super({});
|
|
|
|
this._resolver = new ModelReferenceResolver(this);
|
|
}
|
|
|
|
public loadPackage(apiJsonFilename: string): ApiPackage {
|
|
const apiPackage: ApiPackage = ApiPackage.loadFromJsonFile(apiJsonFilename);
|
|
this.addMember(apiPackage);
|
|
return apiPackage;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
public override get kind(): ApiItemKind {
|
|
return ApiItemKind.Model;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/class-literal-property-style
|
|
public override get containerKey(): string {
|
|
return '';
|
|
}
|
|
|
|
public get packages(): readonly ApiPackage[] {
|
|
return this.members as readonly ApiPackage[];
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
public override addMember(member: ApiPackage): void {
|
|
if (member.kind !== ApiItemKind.Package) {
|
|
throw new Error('Only items of type ApiPackage may be added to an ApiModel');
|
|
}
|
|
|
|
super.addMember(member);
|
|
this._packagesByName = undefined; // invalidate the cache
|
|
this._apiItemsByCanonicalReference = undefined; // invalidate the cache
|
|
}
|
|
|
|
/**
|
|
* Efficiently finds a package by the NPM package name.
|
|
*
|
|
* @remarks
|
|
*
|
|
* If the NPM scope is omitted in the package name, it will still be found provided that it is an unambiguous match.
|
|
* For example, it's often convenient to write `{@link node-core-library#JsonFile}` instead of
|
|
* `{@link @rushstack/node-core-library#JsonFile}`.
|
|
*/
|
|
public tryGetPackageByName(packageName: string): ApiPackage | undefined {
|
|
// Build the lookup on demand
|
|
if (this._packagesByName === undefined) {
|
|
this._packagesByName = new Map<string, ApiPackage>();
|
|
|
|
const unscopedMap: Map<string, ApiPackage | undefined> = new Map<string, ApiPackage | undefined>();
|
|
|
|
for (const apiPackage of this.packages) {
|
|
if (this._packagesByName.get(apiPackage.name)) {
|
|
// This should not happen
|
|
throw new Error(`The model contains multiple packages with the name ${apiPackage.name}`);
|
|
}
|
|
|
|
this._packagesByName.set(apiPackage.name, apiPackage);
|
|
|
|
const unscopedName: string = PackageName.parse(apiPackage.name).unscopedName;
|
|
|
|
if (unscopedMap.has(unscopedName)) {
|
|
// If another package has the same unscoped name, then we won't register it
|
|
unscopedMap.set(unscopedName, undefined);
|
|
} else {
|
|
unscopedMap.set(unscopedName, apiPackage);
|
|
}
|
|
}
|
|
|
|
for (const [unscopedName, apiPackage] of unscopedMap) {
|
|
if (apiPackage && !this._packagesByName.has(unscopedName)) {
|
|
// If the unscoped name is unambiguous, then we can also use it as a lookup
|
|
this._packagesByName.set(unscopedName, apiPackage);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this._packagesByName.get(packageName);
|
|
}
|
|
|
|
public resolveDeclarationReference(
|
|
declarationReference: DeclarationReference | DocDeclarationReference,
|
|
contextApiItem: ApiItem | undefined,
|
|
): IResolveDeclarationReferenceResult {
|
|
if (declarationReference instanceof DocDeclarationReference) {
|
|
return this._resolver.resolve(declarationReference, contextApiItem);
|
|
} else if (declarationReference instanceof DeclarationReference) {
|
|
// use this._apiItemsByCanonicalReference to look up ApiItem
|
|
|
|
// Build the lookup on demand
|
|
if (!this._apiItemsByCanonicalReference) {
|
|
this._apiItemsByCanonicalReference = new Map<string, ApiItem>();
|
|
|
|
for (const apiPackage of this.packages) {
|
|
this._initApiItemsRecursive(apiPackage, this._apiItemsByCanonicalReference);
|
|
}
|
|
}
|
|
|
|
const result: IResolveDeclarationReferenceResult = {
|
|
resolvedApiItem: undefined,
|
|
errorMessage: undefined,
|
|
};
|
|
|
|
const apiItem: ApiItem | undefined = this._apiItemsByCanonicalReference.get(declarationReference.toString());
|
|
|
|
if (apiItem) {
|
|
result.resolvedApiItem = apiItem;
|
|
} else {
|
|
result.errorMessage = `${declarationReference.toString()} can not be located`;
|
|
}
|
|
|
|
return result;
|
|
} else {
|
|
// NOTE: The "instanceof DeclarationReference" test assumes a specific version of the @microsoft/tsdoc package.
|
|
throw new TypeError(
|
|
'The "declarationReference" parameter must be an instance of' +
|
|
' DocDeclarationReference or DeclarationReference',
|
|
);
|
|
}
|
|
}
|
|
|
|
private _initApiItemsRecursive(apiItem: ApiItem, apiItemsByCanonicalReference: Map<string, ApiItem>): void {
|
|
if (apiItem.canonicalReference && !apiItem.canonicalReference.isEmpty) {
|
|
apiItemsByCanonicalReference.set(apiItem.canonicalReference.toString(), apiItem);
|
|
}
|
|
|
|
// Recurse container members
|
|
if (ApiItemContainerMixin.isBaseClassOf(apiItem)) {
|
|
for (const apiMember of apiItem.members) {
|
|
this._initApiItemsRecursive(apiMember, apiItemsByCanonicalReference);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @beta @override
|
|
*/
|
|
public override buildCanonicalReference(): DeclarationReference {
|
|
return DeclarationReference.empty();
|
|
}
|
|
}
|