build: package api-extractor and -model (#9920)

* 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
This commit is contained in:
Qjuh
2023-11-07 21:53:36 +01:00
committed by GitHub
parent 95c0b1a59f
commit 5c0fad3b2d
251 changed files with 36017 additions and 296 deletions

View File

@@ -0,0 +1,225 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { Excerpt, ExcerptToken, type IExcerptTokenRange, type IExcerptToken } from '../mixins/Excerpt.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { SourceLocation } from '../model/SourceLocation.js';
import { ApiDocumentedItem, type IApiDocumentedItemJson, type IApiDocumentedItemOptions } from './ApiDocumentedItem.js';
import type { ApiItem } from './ApiItem.js';
/**
* Constructor options for {@link ApiDeclaredItem}.
*
* @public
*/
export interface IApiDeclaredItemOptions extends IApiDocumentedItemOptions {
excerptTokens: IExcerptToken[];
fileColumn?: number | undefined;
fileLine?: number | undefined;
fileUrlPath?: string | undefined;
}
export interface IApiDeclaredItemJson extends IApiDocumentedItemJson {
excerptTokens: IExcerptToken[];
fileColumn?: number;
fileLine?: number;
fileUrlPath?: string | undefined;
}
/**
* The base class for API items that have an associated source code excerpt containing a TypeScript declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* Most `ApiItem` subclasses have declarations and thus extend `ApiDeclaredItem`. Counterexamples include
* `ApiModel` and `ApiPackage`, which do not have any corresponding TypeScript source code.
* @public
*/
export class ApiDeclaredItem extends ApiDocumentedItem {
private readonly _excerptTokens: ExcerptToken[];
private readonly _excerpt: Excerpt;
private readonly _fileUrlPath?: string | undefined;
private readonly _fileLine?: number | undefined;
private readonly _fileColumn?: number | undefined;
private _sourceLocation?: SourceLocation;
public constructor(options: IApiDeclaredItemOptions) {
super(options);
this._excerptTokens = options.excerptTokens.map((token) => {
const canonicalReference: DeclarationReference | undefined =
token.canonicalReference === undefined ? undefined : DeclarationReference.parse(token.canonicalReference);
return new ExcerptToken(token.kind, token.text, canonicalReference);
});
this._excerpt = new Excerpt(this.excerptTokens, { startIndex: 0, endIndex: this.excerptTokens.length });
this._fileUrlPath = options.fileUrlPath;
this._fileLine = options.fileLine;
this._fileColumn = options.fileColumn;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiDeclaredItemOptions>,
context: DeserializerContext,
jsonObject: IApiDeclaredItemJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
options.excerptTokens = jsonObject.excerptTokens;
options.fileUrlPath = jsonObject.fileUrlPath;
options.fileLine = jsonObject.fileLine;
options.fileColumn = jsonObject.fileColumn;
}
/**
* The source code excerpt where the API item is declared.
*/
public get excerpt(): Excerpt {
return this._excerpt;
}
/**
* The individual source code tokens that comprise the main excerpt.
*/
public get excerptTokens(): readonly ExcerptToken[] {
return this._excerptTokens;
}
/**
* The file URL path relative to the `projectFolder` and `projectFolderURL` fields
* as defined in the `api-extractor.json` config. Is `undefined` if the path is
* the same as the parent API item's.
*/
public get fileUrlPath(): string | undefined {
return this._fileUrlPath;
}
/**
* The line in the `fileUrlPath` where the API item is declared.
*/
public get fileLine(): number | undefined {
return this._fileLine;
}
/**
* The column in the `fileUrlPath` where the API item is declared.
*/
public get fileColumn(): number | undefined {
return this._fileColumn;
}
/**
* Returns the source location where the API item is declared.
*/
public get sourceLocation(): SourceLocation {
if (!this._sourceLocation) {
this._sourceLocation = this._buildSourceLocation();
}
return this._sourceLocation;
}
/**
* If the API item has certain important modifier tags such as `@sealed`, `@virtual`, or `@override`,
* this prepends them as a doc comment above the excerpt.
*/
public getExcerptWithModifiers(): string {
const excerpt: string = this.excerpt.text;
const modifierTags: string[] = [];
if (excerpt.length > 0 && this instanceof ApiDocumentedItem) {
if (this.tsdocComment) {
if (this.tsdocComment.modifierTagSet.isSealed()) {
modifierTags.push('@sealed');
}
if (this.tsdocComment.modifierTagSet.isVirtual()) {
modifierTags.push('@virtual');
}
if (this.tsdocComment.modifierTagSet.isOverride()) {
modifierTags.push('@override');
}
}
if (modifierTags.length > 0) {
return '/** ' + modifierTags.join(' ') + ' */\n' + excerpt;
}
}
return excerpt;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiDeclaredItemJson>): void {
super.serializeInto(jsonObject);
jsonObject.excerptTokens = this.excerptTokens.map((x) => {
const excerptToken: IExcerptToken = { kind: x.kind, text: x.text };
if (x.canonicalReference !== undefined) {
excerptToken.canonicalReference = x.canonicalReference.toString();
}
return excerptToken;
});
// Only serialize this API item's file URL path if it exists and it's different from its parent's
// (a little optimization to keep the doc model succinct).
if (
this.fileUrlPath &&
(!(this.parent instanceof ApiDeclaredItem) || this.fileUrlPath !== this.parent.fileUrlPath)
) {
jsonObject.fileUrlPath = this.fileUrlPath;
}
if (this.fileLine) {
jsonObject.fileLine = this.fileLine;
}
if (this.fileColumn) {
jsonObject.fileColumn = this.fileColumn;
}
}
/**
* Constructs a new {@link Excerpt} corresponding to the provided token range.
*/
public buildExcerpt(tokenRange: IExcerptTokenRange): Excerpt {
return new Excerpt(this.excerptTokens, tokenRange);
}
/**
* Builds the cached object used by the `sourceLocation` property.
*/
private _buildSourceLocation(): SourceLocation {
const projectFolderUrl: string | undefined = this.getAssociatedPackage()?.projectFolderUrl;
let fileUrlPath: string | undefined;
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (current instanceof ApiDeclaredItem && current.fileUrlPath) {
fileUrlPath = current.fileUrlPath;
break;
}
}
return new SourceLocation({
projectFolderUrl,
fileUrlPath,
sourceFileColumn: this.fileColumn,
sourceFileLine: this.fileLine,
});
}
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as tsdoc from '@microsoft/tsdoc';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { ApiItem, type IApiItemOptions, type IApiItemJson } from './ApiItem.js';
/**
* Constructor options for {@link ApiDocumentedItem}.
*
* @public
*/
export interface IApiDocumentedItemOptions extends IApiItemOptions {
docComment: tsdoc.DocComment | undefined;
}
export interface IApiDocumentedItemJson extends IApiItemJson {
docComment: string;
}
/**
* An abstract base class for API declarations that can have an associated TSDoc comment.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
* @public
*/
export class ApiDocumentedItem extends ApiItem {
private readonly _tsdocComment: tsdoc.DocComment | undefined;
public constructor(options: IApiDocumentedItemOptions) {
super(options);
this._tsdocComment = options.docComment;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiDocumentedItemOptions>,
context: DeserializerContext,
jsonObject: IApiItemJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
const documentedJson: IApiDocumentedItemJson = jsonObject as IApiDocumentedItemJson;
if (documentedJson.docComment) {
const tsdocParser: tsdoc.TSDocParser = new tsdoc.TSDocParser(context.tsdocConfiguration);
// NOTE: For now, we ignore TSDoc errors found in a serialized .api.json file.
// Normally these errors would have already been reported by API Extractor during analysis.
// However, they could also arise if the JSON file was edited manually, or if the file was saved
// using a different release of the software that used an incompatible syntax.
const parserContext: tsdoc.ParserContext = tsdocParser.parseString(documentedJson.docComment);
options.docComment = parserContext.docComment;
}
}
public get tsdocComment(): tsdoc.DocComment | undefined {
return this._tsdocComment;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiDocumentedItemJson>): void {
super.serializeInto(jsonObject);
if (this.tsdocComment === undefined) {
jsonObject.docComment = '';
} else {
jsonObject.docComment = this.tsdocComment.emitAsTsdoc();
}
}
}

View File

@@ -0,0 +1,367 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { InternalError } from '@rushstack/node-core-library';
import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin.js';
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 { DeserializerContext } from '../model/DeserializerContext.js';
/**
* The type returned by the {@link ApiItem.kind} property, which can be used to easily distinguish subclasses of
* {@link ApiItem}.
*
* @public
*/
export enum ApiItemKind {
CallSignature = 'CallSignature',
Class = 'Class',
ConstructSignature = 'ConstructSignature',
Constructor = 'Constructor',
EntryPoint = 'EntryPoint',
Enum = 'Enum',
EnumMember = 'EnumMember',
Function = 'Function',
IndexSignature = 'IndexSignature',
Interface = 'Interface',
Method = 'Method',
MethodSignature = 'MethodSignature',
Model = 'Model',
Namespace = 'Namespace',
None = 'None',
Package = 'Package',
Property = 'Property',
PropertySignature = 'PropertySignature',
TypeAlias = 'TypeAlias',
Variable = 'Variable',
}
/**
* Indicates the symbol table from which to resolve the next symbol component.
*
* @beta
*/
export enum Navigation {
Exports = '.',
Locals = '~',
Members = '#',
}
/**
* @beta
*/
export enum Meaning {
CallSignature = 'call',
Class = 'class',
ComplexType = 'complex',
ConstructSignature = 'new',
Constructor = 'constructor',
Enum = 'enum',
Event = 'event',
Function = 'function',
IndexSignature = 'index',
Interface = 'interface',
Member = 'member',
Namespace = 'namespace',
TypeAlias = 'type',
Variable = 'var',
}
/**
* Constructor options for {@link ApiItem}.
*
* @public
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IApiItemOptions {}
export interface IApiItemJson {
canonicalReference: string;
kind: ApiItemKind;
}
// PRIVATE - Allows ApiItemContainerMixin to assign the parent.
//
export const apiItem_onParentChanged: unique symbol = Symbol('ApiItem._onAddToContainer');
/**
* The abstract base class for all members of an `ApiModel` object.
*
* @remarks
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
* @public
*/
export class ApiItem {
private _canonicalReference: DeclarationReference | undefined;
private _parent: ApiItem | undefined;
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
public constructor(_options: IApiItemOptions) {
// ("options" is not used here, but part of the inheritance pattern)
}
public static deserialize(jsonObject: IApiItemJson, context: DeserializerContext): ApiItem {
// 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');
return deserializerModule.Deserializer.deserialize(context, jsonObject);
}
/**
* @virtual
*/
public static onDeserializeInto(
_options: Partial<IApiItemOptions>,
_context: DeserializerContext,
_jsonObject: IApiItemJson,
): void {
// (implemented by subclasses)
}
/**
* @virtual
*/
public serializeInto(jsonObject: Partial<IApiItemJson>): void {
jsonObject.kind = this.kind;
jsonObject.canonicalReference = this.canonicalReference.toString();
}
/**
* Identifies the subclass of the `ApiItem` base class.
*
* @virtual
*/
public get kind(): ApiItemKind {
throw new Error('ApiItem.kind was not implemented by the child class');
}
/**
* Warning: This API is used internally by API extractor but is not yet ready for general usage.
*
* @remarks
*
* Returns a `DeclarationReference` object using the experimental new declaration reference notation.
* @beta
*/
public get canonicalReference(): DeclarationReference {
if (!this._canonicalReference) {
try {
this._canonicalReference = this.buildCanonicalReference();
} catch (error) {
const name: string = this.getScopedNameWithinPackage() || this.displayName;
throw new InternalError(`Error building canonical reference for ${name}:\n` + (error as Error).message);
}
}
return this._canonicalReference;
}
/**
* Returns a string key that can be used to efficiently retrieve an `ApiItem` from an `ApiItemContainerMixin`.
* The key is unique within the container. Its format is undocumented and may change at any time.
*
* @remarks
* Use the `getContainerKey()` static member to construct the key. Each subclass has a different implementation
* of this function, according to the aspects that are important for identifying it.
* @virtual
*/
public get containerKey(): string {
throw new InternalError('ApiItem.containerKey was not implemented by the child class');
}
/**
* Returns a name for this object that can be used in diagnostic messages, for example.
*
* @remarks
* For an object that inherits ApiNameMixin, this will return the declared name (e.g. the name of a TypeScript
* function). Otherwise, it will return a string such as "(call signature)" or "(model)".
* @virtual
*/
public get displayName(): string {
switch (this.kind) {
case ApiItemKind.CallSignature:
return '(call)';
case ApiItemKind.Constructor:
return '(constructor)';
case ApiItemKind.ConstructSignature:
return '(new)';
case ApiItemKind.IndexSignature:
return '(indexer)';
case ApiItemKind.Model:
return '(model)';
default:
return '(???)'; // All other types should inherit ApiNameMixin which will override this property
}
}
/**
* If this item was added to a ApiItemContainerMixin item, then this returns the container item.
* If this is an Parameter that was added to a method or function, then this returns the function item.
* Otherwise, it returns undefined.
*
* @virtual
*/
public get parent(): ApiItem | undefined {
return this._parent;
}
/**
* This property supports a visitor pattern for walking the tree.
* For items with ApiItemContainerMixin, it returns the contained items, sorted alphabetically.
* Otherwise it returns an empty array.
*
* @virtual
*/
public get members(): readonly ApiItem[] {
return [];
}
/**
* If this item has a name (i.e. extends `ApiNameMixin`), then return all items that have the same parent
* and the same name. Otherwise, return all items that have the same parent and the same `ApiItemKind`.
*
* @remarks
* Examples: For a function, this would return all overloads for the function. For a constructor, this would
* return all overloads for the constructor. For a merged declaration (e.g. a `namespace` and `enum` with the
* same name), this would return both declarations. If this item does not have a parent, or if it is the only
* item of its name/kind, then the result is an array containing only this item.
*/
public getMergedSiblings(): readonly ApiItem[] {
const parent: ApiItem | undefined = this._parent;
if (parent && ApiItemContainerMixin.isBaseClassOf(parent)) {
return parent._getMergedSiblingsForMember(this);
}
return [];
}
/**
* Returns the chain of ancestors, starting from the root of the tree, and ending with the this item.
*/
public getHierarchy(): readonly ApiItem[] {
const hierarchy: ApiItem[] = [];
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
hierarchy.push(current);
}
hierarchy.reverse();
return hierarchy;
}
/**
* This returns a scoped name such as `"Namespace1.Namespace2.MyClass.myMember()"`. It does not include the
* package name or entry point.
*
* @remarks
* If called on an ApiEntrypoint, ApiPackage, or ApiModel item, the result is an empty string.
*/
public getScopedNameWithinPackage(): string {
const reversedParts: string[] = [];
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (
current.kind === ApiItemKind.Model ||
current.kind === ApiItemKind.Package ||
current.kind === ApiItemKind.EntryPoint
) {
break;
}
if (reversedParts.length === 0) {
switch (current.kind) {
case ApiItemKind.CallSignature:
case ApiItemKind.ConstructSignature:
case ApiItemKind.Constructor:
case ApiItemKind.IndexSignature:
// These functional forms don't have a proper name, so we don't append the "()" suffix
break;
default:
if (ApiParameterListMixin.isBaseClassOf(current)) {
reversedParts.push('()');
}
}
} else {
reversedParts.push('.');
}
reversedParts.push(current.displayName);
}
return reversedParts.reverse().join('');
}
/**
* If this item is an ApiPackage or has an ApiPackage as one of its parents, then that object is returned.
* Otherwise undefined is returned.
*/
public getAssociatedPackage(): ApiPackage | undefined {
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (current.kind === ApiItemKind.Package) {
return current as ApiPackage;
}
}
return undefined;
}
/**
* If this item is an ApiModel or has an ApiModel as one of its parents, then that object is returned.
* Otherwise undefined is returned.
*/
public getAssociatedModel(): ApiModel | undefined {
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (current.kind === ApiItemKind.Model) {
return current as ApiModel;
}
}
return undefined;
}
/**
* A text string whose value determines the sort order that is automatically applied by the
* {@link (ApiItemContainerMixin:interface)} class.
*
* @remarks
* The value of this string is undocumented and may change at any time.
* If {@link (ApiItemContainerMixin:interface).preserveMemberOrder} is enabled for the `ApiItem`'s parent,
* then no sorting is performed, and this key is not used.
* @virtual
*/
public getSortKey(): string {
return this.containerKey;
}
/**
* PRIVATE
*
* @privateRemarks
* Allows ApiItemContainerMixin to assign the parent when the item is added to a container.
* @internal
*/
public [apiItem_onParentChanged](parent: ApiItem | undefined): void {
this._parent = parent;
this._canonicalReference = undefined;
}
/**
* Builds the cached object used by the `canonicalReference` property.
*
* @virtual
*/
protected buildCanonicalReference(): DeclarationReference {
throw new InternalError('ApiItem.canonicalReference was not implemented by the child class');
}
}
/**
* This abstraction is used by the mixin pattern.
* It describes a class type that inherits from {@link ApiItem}.
*
* @public
*/
export interface IApiItemConstructor extends Constructor<ApiItem>, PropertiesOf<typeof ApiItem> {}

View File

@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiOptionalMixin, type IApiOptionalMixinOptions } from '../mixins/ApiOptionalMixin.js';
import { ApiReadonlyMixin, type IApiReadonlyMixinOptions } from '../mixins/ApiReadonlyMixin.js';
import { ApiReleaseTagMixin, type IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin.js';
import type { Excerpt, IExcerptTokenRange } from '../mixins/Excerpt.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem, type IApiDeclaredItemJson } from './ApiDeclaredItem.js';
/**
* Constructor options for {@link ApiPropertyItem}.
*
* @public
*/
export interface IApiPropertyItemOptions
extends IApiNameMixinOptions,
IApiReleaseTagMixinOptions,
IApiOptionalMixinOptions,
IApiReadonlyMixinOptions,
IApiDeclaredItemOptions {
propertyTypeTokenRange: IExcerptTokenRange;
}
export interface IApiPropertyItemJson extends IApiDeclaredItemJson {
propertyTypeTokenRange: IExcerptTokenRange;
}
/**
* The abstract base class for {@link ApiProperty} and {@link ApiPropertySignature}.
*
* @public
*/
export class ApiPropertyItem extends ApiNameMixin(
ApiReleaseTagMixin(ApiOptionalMixin(ApiReadonlyMixin(ApiDeclaredItem))),
) {
/**
* An {@link Excerpt} that describes the type of the property.
*/
public readonly propertyTypeExcerpt: Excerpt;
public constructor(options: IApiPropertyItemOptions) {
super(options);
this.propertyTypeExcerpt = this.buildExcerpt(options.propertyTypeTokenRange);
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiPropertyItemOptions>,
context: DeserializerContext,
jsonObject: IApiPropertyItemJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
options.propertyTypeTokenRange = jsonObject.propertyTypeTokenRange;
}
/**
* Returns true if this property should be documented as an event.
*
* @remarks
* The `@eventProperty` TSDoc modifier can be added to readonly properties to indicate that they return an
* event object that event handlers can be attached to. The event-handling API is implementation-defined, but
* typically the return type would be a class with members such as `addHandler()` and `removeHandler()`.
* The documentation should display such properties under an "Events" heading instead of the
* usual "Properties" heading.
*/
public get isEventProperty(): boolean {
if (this.tsdocComment) {
return this.tsdocComment.modifierTagSet.isEventProperty();
}
return false;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiPropertyItemJson>): void {
super.serializeInto(jsonObject);
jsonObject.propertyTypeTokenRange = this.propertyTypeExcerpt.tokenRange;
}
}