Skip to content

[api-extractor] Add support for type alias types and type parameters #1317

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 10, 2019
Merged
42 changes: 38 additions & 4 deletions apps/api-documenter/src/documenters/YamlDocumenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import {
ApiMethodSignature,
ApiConstructor,
ApiFunction,
ApiReturnTypeMixin
ApiReturnTypeMixin,
ApiTypeParameterListMixin
} from '@microsoft/api-extractor-model';

import {
Expand Down Expand Up @@ -328,11 +329,11 @@ export class YamlDocumenter {
break;
case ApiItemKind.Class:
yamlItem.type = 'class';
this._populateYamlClassOrInterface(yamlItem, apiItem);
this._populateYamlClassOrInterface(yamlItem, apiItem as ApiClass);
break;
case ApiItemKind.Interface:
yamlItem.type = 'interface';
this._populateYamlClassOrInterface(yamlItem, apiItem);
this._populateYamlClassOrInterface(yamlItem, apiItem as ApiInterface);
break;
case ApiItemKind.Method:
case ApiItemKind.MethodSignature:
Expand Down Expand Up @@ -379,7 +380,27 @@ export class YamlDocumenter {
return yamlItem as IYamlItem;
}

private _populateYamlClassOrInterface(yamlItem: Partial<IYamlItem>, apiItem: ApiDocumentedItem): void {
private _populateYamlTypeParameters(apiItem: ApiTypeParameterListMixin): IYamlParameter[] {
const typeParameters: IYamlParameter[] = [];
for (const apiTypeParameter of apiItem.typeParameters) {
const typeParameter: IYamlParameter = {
id: apiTypeParameter.name
};

if (apiTypeParameter.tsdocTypeParamBlock) {
typeParameter.description = this._renderMarkdown(apiTypeParameter.tsdocTypeParamBlock.content, apiItem);
}

if (!apiTypeParameter.constraintExcerpt.isEmpty) {
typeParameter.type = [ this._linkToUidIfPossible(apiTypeParameter.constraintExcerpt.text) ];
}

typeParameters.push(typeParameter);
}
return typeParameters;
}

private _populateYamlClassOrInterface(yamlItem: Partial<IYamlItem>, apiItem: ApiClass | ApiInterface): void {
if (apiItem instanceof ApiClass) {
if (apiItem.extendsType) {
yamlItem.extends = [ this._linkToUidIfPossible(apiItem.extendsType.excerpt.text) ];
Expand All @@ -397,6 +418,11 @@ export class YamlDocumenter {
yamlItem.extends.push(this._linkToUidIfPossible(extendsType.excerpt.text));
}
}

const typeParameters: IYamlParameter[] = this._populateYamlTypeParameters(apiItem);
if (typeParameters.length) {
yamlItem.syntax = { typeParameters };
}
}

if (apiItem.tsdocComment) {
Expand Down Expand Up @@ -462,6 +488,14 @@ export class YamlDocumenter {
if (parameters.length) {
syntax.parameters = parameters;
}

if (ApiTypeParameterListMixin.isBaseClassOf(apiItem)) {
const typeParameters: IYamlParameter[] = this._populateYamlTypeParameters(apiItem);
if (typeParameters.length) {
syntax.typeParameters = typeParameters;
}
}

}

private _populateYamlProperty(yamlItem: Partial<IYamlItem>, apiItem: ApiPropertyItem): void {
Expand Down
3 changes: 2 additions & 1 deletion apps/api-documenter/src/yaml/IYamlApiFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface IYamlException {
* Part of the IYamlApiFile structure. Represents the type of an IYamlItem.
*/
export type YamlTypeId = 'class' | 'constructor' | 'enum' | 'field' | 'function' | 'interface'
| 'method' | 'package' | 'property' | 'event';
| 'method' | 'package' | 'property' | 'event' | 'typealias' | 'variable';

/**
* Part of the IYamlApiFile structure. Represents basic API elements such as
Expand Down Expand Up @@ -112,5 +112,6 @@ export interface IYamlSource {
export interface IYamlSyntax {
content?: string;
parameters?: IYamlParameter[];
typeParameters?: IYamlParameter[];
return?: IYamlReturn;
}
10 changes: 9 additions & 1 deletion apps/api-documenter/src/yaml/typescript.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
"$ref": "#/definitions/syntax"
},
"type": {
"enum": [ "class", "constructor", "enum", "field", "function", "interface", "method", "package", "property", "event" ]
"enum": [ "class", "constructor", "enum", "field", "function", "interface", "method", "package", "property", "event", "typealias", "variable" ]
},
"uid": {
"type": "string",
Expand Down Expand Up @@ -221,6 +221,14 @@
"content": {
"type": "string"
},
"typeParameters": {
"type": "array",
"items": {
"$ref": "#/definitions/parameter"
},
"minItems": 1,
"uniqueItems": true
},
"parameters": {
"type": "array",
"items": {
Expand Down
13 changes: 13 additions & 0 deletions apps/api-extractor-model/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export {
IApiParameterOptions,
ApiParameterListMixin
} from './mixins/ApiParameterListMixin';
export {
IApiTypeParameterOptions,
IApiTypeParameterListMixinOptions,
ApiTypeParameterListMixin
} from './mixins/ApiTypeParameterListMixin';
export {
IApiItemContainerMixinOptions,
ApiItemContainerMixin
Expand All @@ -54,6 +59,10 @@ export {
IApiStaticMixinOptions,
ApiStaticMixin
} from './mixins/ApiStaticMixin';
export {
IApiNameMixinOptions,
ApiNameMixin
} from './mixins/ApiNameMixin';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, somehow we forgot to export this

export {
ExcerptTokenKind,
IExcerptTokenRange,
Expand Down Expand Up @@ -143,6 +152,10 @@ export {
IApiTypeAliasOptions,
ApiTypeAlias
} from './model/ApiTypeAlias';
export {
ITypeParameterOptions,
TypeParameter
} from './model/TypeParameter';
export {
IApiVariableOptions,
ApiVariable
Expand Down
155 changes: 155 additions & 0 deletions apps/api-extractor-model/src/mixins/ApiTypeParameterListMixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.s

import { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem';
import { IExcerptTokenRange } from './Excerpt';
import { TypeParameter } from '../model/TypeParameter';
import { InternalError } from '@microsoft/node-core-library';
import { ApiDeclaredItem } from '../items/ApiDeclaredItem';

/**
* Represents parameter information that is part of {@link IApiTypeParameterListMixinOptions}
* @public
*/
export interface IApiTypeParameterOptions {
typeParameterName: string;
constraintTokenRange: IExcerptTokenRange;
defaultTypeTokenRange: IExcerptTokenRange;
}

/**
* Constructor options for {@link (ApiTypeParameterListMixin:interface)}.
* @public
*/
export interface IApiTypeParameterListMixinOptions extends IApiItemOptions {
typeParameters: IApiTypeParameterOptions[];
}

export interface IApiTypeParameterListMixinJson extends IApiItemJson {
typeParameters: IApiTypeParameterOptions[];
}

const _typeParameters: unique symbol = Symbol('ApiTypeParameterListMixin._typeParameters');

/**
* The mixin base class for API items that can have type parameters.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
*
* @public
*/
// tslint:disable-next-line:interface-name
export interface ApiTypeParameterListMixin extends ApiItem {
/**
* The type parameters.
*/
readonly typeParameters: ReadonlyArray<TypeParameter>;

serializeInto(jsonObject: Partial<IApiItemJson>): void;
}

/**
* Mixin function for {@link (ApiTypeParameterListMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiTypeParameterListMixin:interface)}
* functionality.
*
* @public
*/
export function ApiTypeParameterListMixin<TBaseClass extends IApiItemConstructor>(baseClass: TBaseClass):
TBaseClass & (new (...args: any[]) => ApiTypeParameterListMixin) { // tslint:disable-line:no-any

abstract class MixedClass extends baseClass implements ApiTypeParameterListMixin {
public readonly [_typeParameters]: TypeParameter[];

/** @override */
public static onDeserializeInto(options: Partial<IApiTypeParameterListMixinOptions>,
jsonObject: IApiTypeParameterListMixinJson): void {

baseClass.onDeserializeInto(options, jsonObject);

options.typeParameters = jsonObject.typeParameters || [];
}

// tslint:disable-next-line:no-any
constructor(...args: any[]) {
super(...args);

const options: IApiTypeParameterListMixinOptions = args[0];

this[_typeParameters] = [];

if (this instanceof ApiDeclaredItem) {
if (options.typeParameters) {
for (const typeParameterOptions of options.typeParameters) {

const typeParameter: TypeParameter = new TypeParameter({
name: typeParameterOptions.typeParameterName,
constraintExcerpt: this.buildExcerpt(typeParameterOptions.constraintTokenRange),
defaultTypeExcerpt: this.buildExcerpt(typeParameterOptions.defaultTypeTokenRange),
parent: this
});

this[_typeParameters].push(typeParameter);
}
}
} else {
throw new InternalError('ApiTypeParameterListMixin expects a base class that inherits from ApiDeclaredItem');
}
}

public get typeParameters(): ReadonlyArray<TypeParameter> {
return this[_typeParameters];
}

/** @override */
public serializeInto(jsonObject: Partial<IApiTypeParameterListMixinJson>): void {
super.serializeInto(jsonObject);

const typeParameterObjects: IApiTypeParameterOptions[] = [];
for (const typeParameter of this.typeParameters) {
typeParameterObjects.push(
{
typeParameterName: typeParameter.name,
constraintTokenRange: typeParameter.constraintExcerpt.tokenRange,
defaultTypeTokenRange: typeParameter.defaultTypeExcerpt.tokenRange
}
);
}

if (typeParameterObjects.length > 0) {
jsonObject.typeParameters = typeParameterObjects;
}
}
}

return MixedClass;
}

/**
* Static members for {@link (ApiTypeParameterListMixin:interface)}.
* @public
*/
export namespace ApiTypeParameterListMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiParameterListMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiTypeParameterListMixin {
return apiItem.hasOwnProperty(_typeParameters);
}
}
4 changes: 4 additions & 0 deletions apps/api-extractor-model/src/mixins/Excerpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,8 @@ export class Excerpt {
}
return this._text;
}

public get isEmpty(): boolean {
return this.tokenRange.startIndex === this.tokenRange.endIndex;
}
}
5 changes: 4 additions & 1 deletion apps/api-extractor-model/src/model/ApiCallSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredIt
import { IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin';
import { IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin';
import { IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from '../mixins/ApiReturnTypeMixin';
import { IApiTypeParameterListMixinOptions, ApiTypeParameterListMixin } from '../mixins/ApiTypeParameterListMixin';

/**
* Constructor options for {@link ApiCallSignature}.
* @public
*/
export interface IApiCallSignatureOptions extends
IApiTypeParameterListMixinOptions,
IApiParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiReturnTypeMixinOptions,
Expand Down Expand Up @@ -47,7 +49,8 @@ export interface IApiCallSignatureOptions extends
*
* @public
*/
export class ApiCallSignature extends ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiDeclaredItem))) {
export class ApiCallSignature extends ApiTypeParameterListMixin(ApiParameterListMixin(ApiReleaseTagMixin(
ApiReturnTypeMixin(ApiDeclaredItem)))) {

public static getCanonicalReference(overloadIndex: number): string {
return `(:call,${overloadIndex})`;
Expand Down
12 changes: 9 additions & 3 deletions apps/api-extractor-model/src/model/ApiClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ApiReleaseTagMixin, IApiReleaseTagMixinOptions } from '../mixins/ApiRel
import { IExcerptTokenRange } from '../mixins/Excerpt';
import { HeritageType } from './HeritageType';
import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin';
import { ApiTypeParameterListMixin, IApiTypeParameterListMixinOptions, IApiTypeParameterListMixinJson
} from '../mixins/ApiTypeParameterListMixin';

/**
* Constructor options for {@link ApiClass}.
Expand All @@ -17,13 +19,16 @@ export interface IApiClassOptions extends
IApiItemContainerMixinOptions,
IApiNameMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions {
IApiDeclaredItemOptions,
IApiTypeParameterListMixinOptions {

extendsTokenRange: IExcerptTokenRange | undefined;
implementsTokenRanges: IExcerptTokenRange[];
}

export interface IApiClassJson extends IApiDeclaredItemJson {
export interface IApiClassJson extends
IApiDeclaredItemJson,
IApiTypeParameterListMixinJson {
extendsTokenRange?: IExcerptTokenRange;
implementsTokenRanges: IExcerptTokenRange[];
}
Expand All @@ -44,7 +49,8 @@ export interface IApiClassJson extends IApiDeclaredItemJson {
*
* @public
*/
export class ApiClass extends ApiItemContainerMixin(ApiNameMixin(ApiReleaseTagMixin(ApiDeclaredItem))) {
export class ApiClass extends ApiItemContainerMixin(ApiNameMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(
ApiDeclaredItem)))) {

/**
* The base class that this class inherits from (using the `extends` keyword), or undefined if there is no base class.
Expand Down
Loading