Skip to content

Commit 662db07

Browse files
authored
Merge pull request #1317 from rbuckton/updateApiModel
[api-extractor] Add support for type alias types and type parameters
2 parents 0474edd + 32c9b73 commit 662db07

27 files changed

+1305
-54
lines changed

apps/api-documenter/src/documenters/YamlDocumenter.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import {
3333
ApiMethodSignature,
3434
ApiConstructor,
3535
ApiFunction,
36-
ApiReturnTypeMixin
36+
ApiReturnTypeMixin,
37+
ApiTypeParameterListMixin
3738
} from '@microsoft/api-extractor-model';
3839

3940
import {
@@ -328,11 +329,11 @@ export class YamlDocumenter {
328329
break;
329330
case ApiItemKind.Class:
330331
yamlItem.type = 'class';
331-
this._populateYamlClassOrInterface(yamlItem, apiItem);
332+
this._populateYamlClassOrInterface(yamlItem, apiItem as ApiClass);
332333
break;
333334
case ApiItemKind.Interface:
334335
yamlItem.type = 'interface';
335-
this._populateYamlClassOrInterface(yamlItem, apiItem);
336+
this._populateYamlClassOrInterface(yamlItem, apiItem as ApiInterface);
336337
break;
337338
case ApiItemKind.Method:
338339
case ApiItemKind.MethodSignature:
@@ -379,7 +380,27 @@ export class YamlDocumenter {
379380
return yamlItem as IYamlItem;
380381
}
381382

382-
private _populateYamlClassOrInterface(yamlItem: Partial<IYamlItem>, apiItem: ApiDocumentedItem): void {
383+
private _populateYamlTypeParameters(apiItem: ApiTypeParameterListMixin): IYamlParameter[] {
384+
const typeParameters: IYamlParameter[] = [];
385+
for (const apiTypeParameter of apiItem.typeParameters) {
386+
const typeParameter: IYamlParameter = {
387+
id: apiTypeParameter.name
388+
};
389+
390+
if (apiTypeParameter.tsdocTypeParamBlock) {
391+
typeParameter.description = this._renderMarkdown(apiTypeParameter.tsdocTypeParamBlock.content, apiItem);
392+
}
393+
394+
if (!apiTypeParameter.constraintExcerpt.isEmpty) {
395+
typeParameter.type = [ this._linkToUidIfPossible(apiTypeParameter.constraintExcerpt.text) ];
396+
}
397+
398+
typeParameters.push(typeParameter);
399+
}
400+
return typeParameters;
401+
}
402+
403+
private _populateYamlClassOrInterface(yamlItem: Partial<IYamlItem>, apiItem: ApiClass | ApiInterface): void {
383404
if (apiItem instanceof ApiClass) {
384405
if (apiItem.extendsType) {
385406
yamlItem.extends = [ this._linkToUidIfPossible(apiItem.extendsType.excerpt.text) ];
@@ -397,6 +418,11 @@ export class YamlDocumenter {
397418
yamlItem.extends.push(this._linkToUidIfPossible(extendsType.excerpt.text));
398419
}
399420
}
421+
422+
const typeParameters: IYamlParameter[] = this._populateYamlTypeParameters(apiItem);
423+
if (typeParameters.length) {
424+
yamlItem.syntax = { typeParameters };
425+
}
400426
}
401427

402428
if (apiItem.tsdocComment) {
@@ -462,6 +488,14 @@ export class YamlDocumenter {
462488
if (parameters.length) {
463489
syntax.parameters = parameters;
464490
}
491+
492+
if (ApiTypeParameterListMixin.isBaseClassOf(apiItem)) {
493+
const typeParameters: IYamlParameter[] = this._populateYamlTypeParameters(apiItem);
494+
if (typeParameters.length) {
495+
syntax.typeParameters = typeParameters;
496+
}
497+
}
498+
465499
}
466500

467501
private _populateYamlProperty(yamlItem: Partial<IYamlItem>, apiItem: ApiPropertyItem): void {

apps/api-documenter/src/yaml/IYamlApiFile.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface IYamlException {
2727
* Part of the IYamlApiFile structure. Represents the type of an IYamlItem.
2828
*/
2929
export type YamlTypeId = 'class' | 'constructor' | 'enum' | 'field' | 'function' | 'interface'
30-
| 'method' | 'package' | 'property' | 'event';
30+
| 'method' | 'package' | 'property' | 'event' | 'typealias' | 'variable';
3131

3232
/**
3333
* Part of the IYamlApiFile structure. Represents basic API elements such as
@@ -112,5 +112,6 @@ export interface IYamlSource {
112112
export interface IYamlSyntax {
113113
content?: string;
114114
parameters?: IYamlParameter[];
115+
typeParameters?: IYamlParameter[];
115116
return?: IYamlReturn;
116117
}

apps/api-documenter/src/yaml/typescript.schema.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
"$ref": "#/definitions/syntax"
130130
},
131131
"type": {
132-
"enum": [ "class", "constructor", "enum", "field", "function", "interface", "method", "package", "property", "event" ]
132+
"enum": [ "class", "constructor", "enum", "field", "function", "interface", "method", "package", "property", "event", "typealias", "variable" ]
133133
},
134134
"uid": {
135135
"type": "string",
@@ -221,6 +221,14 @@
221221
"content": {
222222
"type": "string"
223223
},
224+
"typeParameters": {
225+
"type": "array",
226+
"items": {
227+
"$ref": "#/definitions/parameter"
228+
},
229+
"minItems": 1,
230+
"uniqueItems": true
231+
},
224232
"parameters": {
225233
"type": "array",
226234
"items": {

apps/api-extractor-model/src/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export {
3838
IApiParameterOptions,
3939
ApiParameterListMixin
4040
} from './mixins/ApiParameterListMixin';
41+
export {
42+
IApiTypeParameterOptions,
43+
IApiTypeParameterListMixinOptions,
44+
ApiTypeParameterListMixin
45+
} from './mixins/ApiTypeParameterListMixin';
4146
export {
4247
IApiItemContainerMixinOptions,
4348
ApiItemContainerMixin
@@ -54,6 +59,10 @@ export {
5459
IApiStaticMixinOptions,
5560
ApiStaticMixin
5661
} from './mixins/ApiStaticMixin';
62+
export {
63+
IApiNameMixinOptions,
64+
ApiNameMixin
65+
} from './mixins/ApiNameMixin';
5766
export {
5867
ExcerptTokenKind,
5968
IExcerptTokenRange,
@@ -143,6 +152,10 @@ export {
143152
IApiTypeAliasOptions,
144153
ApiTypeAlias
145154
} from './model/ApiTypeAlias';
155+
export {
156+
ITypeParameterOptions,
157+
TypeParameter
158+
} from './model/TypeParameter';
146159
export {
147160
IApiVariableOptions,
148161
ApiVariable
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.s
3+
4+
import { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem';
5+
import { IExcerptTokenRange } from './Excerpt';
6+
import { TypeParameter } from '../model/TypeParameter';
7+
import { InternalError } from '@microsoft/node-core-library';
8+
import { ApiDeclaredItem } from '../items/ApiDeclaredItem';
9+
10+
/**
11+
* Represents parameter information that is part of {@link IApiTypeParameterListMixinOptions}
12+
* @public
13+
*/
14+
export interface IApiTypeParameterOptions {
15+
typeParameterName: string;
16+
constraintTokenRange: IExcerptTokenRange;
17+
defaultTypeTokenRange: IExcerptTokenRange;
18+
}
19+
20+
/**
21+
* Constructor options for {@link (ApiTypeParameterListMixin:interface)}.
22+
* @public
23+
*/
24+
export interface IApiTypeParameterListMixinOptions extends IApiItemOptions {
25+
typeParameters: IApiTypeParameterOptions[];
26+
}
27+
28+
export interface IApiTypeParameterListMixinJson extends IApiItemJson {
29+
typeParameters: IApiTypeParameterOptions[];
30+
}
31+
32+
const _typeParameters: unique symbol = Symbol('ApiTypeParameterListMixin._typeParameters');
33+
34+
/**
35+
* The mixin base class for API items that can have type parameters.
36+
*
37+
* @remarks
38+
*
39+
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
40+
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
41+
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
42+
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
43+
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
44+
* the function that generates a subclass, an interface that describes the members of the subclass, and
45+
* a namespace containing static members of the class.
46+
*
47+
* @public
48+
*/
49+
// tslint:disable-next-line:interface-name
50+
export interface ApiTypeParameterListMixin extends ApiItem {
51+
/**
52+
* The type parameters.
53+
*/
54+
readonly typeParameters: ReadonlyArray<TypeParameter>;
55+
56+
serializeInto(jsonObject: Partial<IApiItemJson>): void;
57+
}
58+
59+
/**
60+
* Mixin function for {@link (ApiTypeParameterListMixin:interface)}.
61+
*
62+
* @param baseClass - The base class to be extended
63+
* @returns A child class that extends baseClass, adding the {@link (ApiTypeParameterListMixin:interface)}
64+
* functionality.
65+
*
66+
* @public
67+
*/
68+
export function ApiTypeParameterListMixin<TBaseClass extends IApiItemConstructor>(baseClass: TBaseClass):
69+
TBaseClass & (new (...args: any[]) => ApiTypeParameterListMixin) { // tslint:disable-line:no-any
70+
71+
abstract class MixedClass extends baseClass implements ApiTypeParameterListMixin {
72+
public readonly [_typeParameters]: TypeParameter[];
73+
74+
/** @override */
75+
public static onDeserializeInto(options: Partial<IApiTypeParameterListMixinOptions>,
76+
jsonObject: IApiTypeParameterListMixinJson): void {
77+
78+
baseClass.onDeserializeInto(options, jsonObject);
79+
80+
options.typeParameters = jsonObject.typeParameters || [];
81+
}
82+
83+
// tslint:disable-next-line:no-any
84+
constructor(...args: any[]) {
85+
super(...args);
86+
87+
const options: IApiTypeParameterListMixinOptions = args[0];
88+
89+
this[_typeParameters] = [];
90+
91+
if (this instanceof ApiDeclaredItem) {
92+
if (options.typeParameters) {
93+
for (const typeParameterOptions of options.typeParameters) {
94+
95+
const typeParameter: TypeParameter = new TypeParameter({
96+
name: typeParameterOptions.typeParameterName,
97+
constraintExcerpt: this.buildExcerpt(typeParameterOptions.constraintTokenRange),
98+
defaultTypeExcerpt: this.buildExcerpt(typeParameterOptions.defaultTypeTokenRange),
99+
parent: this
100+
});
101+
102+
this[_typeParameters].push(typeParameter);
103+
}
104+
}
105+
} else {
106+
throw new InternalError('ApiTypeParameterListMixin expects a base class that inherits from ApiDeclaredItem');
107+
}
108+
}
109+
110+
public get typeParameters(): ReadonlyArray<TypeParameter> {
111+
return this[_typeParameters];
112+
}
113+
114+
/** @override */
115+
public serializeInto(jsonObject: Partial<IApiTypeParameterListMixinJson>): void {
116+
super.serializeInto(jsonObject);
117+
118+
const typeParameterObjects: IApiTypeParameterOptions[] = [];
119+
for (const typeParameter of this.typeParameters) {
120+
typeParameterObjects.push(
121+
{
122+
typeParameterName: typeParameter.name,
123+
constraintTokenRange: typeParameter.constraintExcerpt.tokenRange,
124+
defaultTypeTokenRange: typeParameter.defaultTypeExcerpt.tokenRange
125+
}
126+
);
127+
}
128+
129+
if (typeParameterObjects.length > 0) {
130+
jsonObject.typeParameters = typeParameterObjects;
131+
}
132+
}
133+
}
134+
135+
return MixedClass;
136+
}
137+
138+
/**
139+
* Static members for {@link (ApiTypeParameterListMixin:interface)}.
140+
* @public
141+
*/
142+
export namespace ApiTypeParameterListMixin {
143+
/**
144+
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiParameterListMixin` mixin.
145+
*
146+
* @remarks
147+
*
148+
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
149+
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
150+
* the TypeScript type system cannot invoke a runtime test.)
151+
*/
152+
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiTypeParameterListMixin {
153+
return apiItem.hasOwnProperty(_typeParameters);
154+
}
155+
}

apps/api-extractor-model/src/mixins/Excerpt.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ export class Excerpt {
8787
}
8888
return this._text;
8989
}
90+
91+
public get isEmpty(): boolean {
92+
return this.tokenRange.startIndex === this.tokenRange.endIndex;
93+
}
9094
}

apps/api-extractor-model/src/model/ApiCallSignature.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import { IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredIt
66
import { IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin';
77
import { IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin';
88
import { IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from '../mixins/ApiReturnTypeMixin';
9+
import { IApiTypeParameterListMixinOptions, ApiTypeParameterListMixin } from '../mixins/ApiTypeParameterListMixin';
910

1011
/**
1112
* Constructor options for {@link ApiCallSignature}.
1213
* @public
1314
*/
1415
export interface IApiCallSignatureOptions extends
16+
IApiTypeParameterListMixinOptions,
1517
IApiParameterListMixinOptions,
1618
IApiReleaseTagMixinOptions,
1719
IApiReturnTypeMixinOptions,
@@ -47,7 +49,8 @@ export interface IApiCallSignatureOptions extends
4749
*
4850
* @public
4951
*/
50-
export class ApiCallSignature extends ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiDeclaredItem))) {
52+
export class ApiCallSignature extends ApiTypeParameterListMixin(ApiParameterListMixin(ApiReleaseTagMixin(
53+
ApiReturnTypeMixin(ApiDeclaredItem)))) {
5154

5255
public static getCanonicalReference(overloadIndex: number): string {
5356
return `(:call,${overloadIndex})`;

apps/api-extractor-model/src/model/ApiClass.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { ApiReleaseTagMixin, IApiReleaseTagMixinOptions } from '../mixins/ApiRel
88
import { IExcerptTokenRange } from '../mixins/Excerpt';
99
import { HeritageType } from './HeritageType';
1010
import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin';
11+
import { ApiTypeParameterListMixin, IApiTypeParameterListMixinOptions, IApiTypeParameterListMixinJson
12+
} from '../mixins/ApiTypeParameterListMixin';
1113

1214
/**
1315
* Constructor options for {@link ApiClass}.
@@ -17,13 +19,16 @@ export interface IApiClassOptions extends
1719
IApiItemContainerMixinOptions,
1820
IApiNameMixinOptions,
1921
IApiReleaseTagMixinOptions,
20-
IApiDeclaredItemOptions {
22+
IApiDeclaredItemOptions,
23+
IApiTypeParameterListMixinOptions {
2124

2225
extendsTokenRange: IExcerptTokenRange | undefined;
2326
implementsTokenRanges: IExcerptTokenRange[];
2427
}
2528

26-
export interface IApiClassJson extends IApiDeclaredItemJson {
29+
export interface IApiClassJson extends
30+
IApiDeclaredItemJson,
31+
IApiTypeParameterListMixinJson {
2732
extendsTokenRange?: IExcerptTokenRange;
2833
implementsTokenRanges: IExcerptTokenRange[];
2934
}
@@ -44,7 +49,8 @@ export interface IApiClassJson extends IApiDeclaredItemJson {
4449
*
4550
* @public
4651
*/
47-
export class ApiClass extends ApiItemContainerMixin(ApiNameMixin(ApiReleaseTagMixin(ApiDeclaredItem))) {
52+
export class ApiClass extends ApiItemContainerMixin(ApiNameMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(
53+
ApiDeclaredItem)))) {
4854

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

0 commit comments

Comments
 (0)