@microsoft/api-extractor
Version:
Analyze the exported API for a TypeScript library and generate reviews, documentation, and .d.ts rollups
844 lines • 45.2 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiModelGenerator = void 0;
/* eslint-disable no-bitwise */
const path = __importStar(require("path"));
const ts = __importStar(require("typescript"));
const api_extractor_model_1 = require("@microsoft/api-extractor-model");
const node_core_library_1 = require("@rushstack/node-core-library");
const ExcerptBuilder_1 = require("./ExcerptBuilder");
const AstSymbol_1 = require("../analyzer/AstSymbol");
const DeclarationReferenceGenerator_1 = require("./DeclarationReferenceGenerator");
const AstNamespaceImport_1 = require("../analyzer/AstNamespaceImport");
const TypeScriptInternals_1 = require("../analyzer/TypeScriptInternals");
class ApiModelGenerator {
constructor(collector, extractorConfig) {
this._collector = collector;
this._apiModel = new api_extractor_model_1.ApiModel();
this._referenceGenerator = new DeclarationReferenceGenerator_1.DeclarationReferenceGenerator(collector);
const apiModelGenerationOptions = extractorConfig.docModelGenerationOptions;
if (apiModelGenerationOptions) {
this._releaseTagsToTrim = apiModelGenerationOptions.releaseTagsToTrim;
this.docModelEnabled = true;
}
else {
this.docModelEnabled = false;
}
}
get apiModel() {
return this._apiModel;
}
buildApiPackage() {
const packageDocComment = this._collector.workingPackage.tsdocComment;
const apiPackage = new api_extractor_model_1.ApiPackage({
name: this._collector.workingPackage.name,
docComment: packageDocComment,
tsdocConfiguration: this._collector.extractorConfig.tsdocConfiguration,
projectFolderUrl: this._collector.extractorConfig.projectFolderUrl
});
this._apiModel.addMember(apiPackage);
const apiEntryPoint = new api_extractor_model_1.ApiEntryPoint({ name: '' });
apiPackage.addMember(apiEntryPoint);
for (const entity of this._collector.entities) {
// Only process entities that are exported from the entry point. Entities that are exported from
// `AstNamespaceImport` entities will be processed by `_processAstNamespaceImport`. However, if
// we are including forgotten exports, then process everything.
if (entity.exportedFromEntryPoint || this._collector.extractorConfig.docModelIncludeForgottenExports) {
this._processAstEntity(entity.astEntity, {
name: entity.nameForEmit,
isExported: entity.exportedFromEntryPoint,
parentApiItem: apiEntryPoint
});
}
}
return apiPackage;
}
_processAstEntity(astEntity, context) {
if (astEntity instanceof AstSymbol_1.AstSymbol) {
// Skip ancillary declarations; we will process them with the main declaration
for (const astDeclaration of this._collector.getNonAncillaryDeclarations(astEntity)) {
this._processDeclaration(astDeclaration, context);
}
return;
}
if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
// Note that a single API item can belong to two different AstNamespaceImport namespaces. For example:
//
// // file.ts defines "thing()"
// import * as example1 from "./file";
// import * as example2 from "./file";
//
// // ...so here we end up with example1.thing() and example2.thing()
// export { example1, example2 }
//
// The current logic does not try to associate "thing()" with a specific parent. Instead
// the API documentation will show duplicated entries for example1.thing() and example2.thing().
//
// This could be improved in the future, but it requires a stable mechanism for choosing an associated parent.
// For thoughts about this: https://github.com/microsoft/rushstack/issues/1308
this._processAstNamespaceImport(astEntity, context);
return;
}
// TODO: Figure out how to represent reexported AstImport objects. Basically we need to introduce a new
// ApiItem subclass for "export alias", similar to a type alias, but representing declarations of the
// form "export { X } from 'external-package'". We can also use this to solve GitHub issue #950.
}
_processAstNamespaceImport(astNamespaceImport, context) {
const astModule = astNamespaceImport.astModule;
const { name, isExported, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiNamespace.getContainerKey(name);
const fileUrlPath = this._getFileUrlPath(astNamespaceImport.declaration);
let apiNamespace = parentApiItem.tryGetMemberByKey(containerKey);
if (apiNamespace === undefined) {
apiNamespace = new api_extractor_model_1.ApiNamespace({
name,
docComment: undefined,
releaseTag: api_extractor_model_1.ReleaseTag.None,
excerptTokens: [],
isExported,
fileUrlPath
});
parentApiItem.addMember(apiNamespace);
}
astModule.astModuleExportInfo.exportedLocalEntities.forEach((exportedEntity, exportedName) => {
this._processAstEntity(exportedEntity, {
name: exportedName,
isExported: true,
parentApiItem: apiNamespace
});
});
}
_processDeclaration(astDeclaration, context) {
var _a;
if ((astDeclaration.modifierFlags & ts.ModifierFlags.Private) !== 0) {
return; // trim out private declarations
}
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const releaseTag = apiItemMetadata.effectiveReleaseTag;
if ((_a = this._releaseTagsToTrim) === null || _a === void 0 ? void 0 : _a.has(releaseTag)) {
return;
}
switch (astDeclaration.declaration.kind) {
case ts.SyntaxKind.CallSignature:
this._processApiCallSignature(astDeclaration, context);
break;
case ts.SyntaxKind.Constructor:
this._processApiConstructor(astDeclaration, context);
break;
case ts.SyntaxKind.ConstructSignature:
this._processApiConstructSignature(astDeclaration, context);
break;
case ts.SyntaxKind.ClassDeclaration:
this._processApiClass(astDeclaration, context);
break;
case ts.SyntaxKind.EnumDeclaration:
this._processApiEnum(astDeclaration, context);
break;
case ts.SyntaxKind.EnumMember:
this._processApiEnumMember(astDeclaration, context);
break;
case ts.SyntaxKind.FunctionDeclaration:
this._processApiFunction(astDeclaration, context);
break;
case ts.SyntaxKind.GetAccessor:
this._processApiProperty(astDeclaration, context);
break;
case ts.SyntaxKind.SetAccessor:
this._processApiProperty(astDeclaration, context);
break;
case ts.SyntaxKind.IndexSignature:
this._processApiIndexSignature(astDeclaration, context);
break;
case ts.SyntaxKind.InterfaceDeclaration:
this._processApiInterface(astDeclaration, context);
break;
case ts.SyntaxKind.MethodDeclaration:
this._processApiMethod(astDeclaration, context);
break;
case ts.SyntaxKind.MethodSignature:
this._processApiMethodSignature(astDeclaration, context);
break;
case ts.SyntaxKind.ModuleDeclaration:
this._processApiNamespace(astDeclaration, context);
break;
case ts.SyntaxKind.PropertyDeclaration:
this._processApiProperty(astDeclaration, context);
break;
case ts.SyntaxKind.PropertySignature:
this._processApiPropertySignature(astDeclaration, context);
break;
case ts.SyntaxKind.TypeAliasDeclaration:
this._processApiTypeAlias(astDeclaration, context);
break;
case ts.SyntaxKind.VariableDeclaration:
// check for arrow functions in variable declaration
const functionDeclaration = this._tryFindFunctionDeclaration(astDeclaration);
if (functionDeclaration) {
this._processApiFunction(astDeclaration, context, functionDeclaration);
}
else {
this._processApiVariable(astDeclaration, context);
}
break;
default:
// ignore unknown types
}
}
_tryFindFunctionDeclaration(astDeclaration) {
const children = astDeclaration.declaration.getChildren(astDeclaration.declaration.getSourceFile());
return children.find(ts.isFunctionTypeNode);
}
_processChildDeclarations(astDeclaration, context) {
for (const childDeclaration of astDeclaration.children) {
this._processDeclaration(childDeclaration, Object.assign(Object.assign({}, context), { name: childDeclaration.astSymbol.localName }));
}
}
_processApiCallSignature(astDeclaration, context) {
const { parentApiItem } = context;
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
const containerKey = api_extractor_model_1.ApiCallSignature.getContainerKey(overloadIndex);
let apiCallSignature = parentApiItem.tryGetMemberByKey(containerKey);
if (apiCallSignature === undefined) {
const callSignature = astDeclaration.declaration;
const nodesToCapture = [];
const returnTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: callSignature.type, tokenRange: returnTypeTokenRange });
const typeParameters = this._captureTypeParameters(nodesToCapture, callSignature.typeParameters);
const parameters = this._captureParameters(nodesToCapture, callSignature.parameters);
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const fileUrlPath = this._getFileUrlPath(callSignature);
apiCallSignature = new api_extractor_model_1.ApiCallSignature({
docComment,
releaseTag,
typeParameters,
parameters,
overloadIndex,
excerptTokens,
returnTypeTokenRange,
fileUrlPath
});
parentApiItem.addMember(apiCallSignature);
}
}
_processApiConstructor(astDeclaration, context) {
const { parentApiItem } = context;
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
const containerKey = api_extractor_model_1.ApiConstructor.getContainerKey(overloadIndex);
let apiConstructor = parentApiItem.tryGetMemberByKey(containerKey);
if (apiConstructor === undefined) {
const constructorDeclaration = astDeclaration.declaration;
const nodesToCapture = [];
const parameters = this._captureParameters(nodesToCapture, constructorDeclaration.parameters);
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const isProtected = (astDeclaration.modifierFlags & ts.ModifierFlags.Protected) !== 0;
const fileUrlPath = this._getFileUrlPath(constructorDeclaration);
apiConstructor = new api_extractor_model_1.ApiConstructor({
docComment,
releaseTag,
isProtected,
parameters,
overloadIndex,
excerptTokens,
fileUrlPath
});
parentApiItem.addMember(apiConstructor);
}
}
_processApiClass(astDeclaration, context) {
const { name, isExported, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiClass.getContainerKey(name);
let apiClass = parentApiItem.tryGetMemberByKey(containerKey);
if (apiClass === undefined) {
const classDeclaration = astDeclaration.declaration;
const nodesToCapture = [];
const typeParameters = this._captureTypeParameters(nodesToCapture, classDeclaration.typeParameters);
let extendsTokenRange = undefined;
const implementsTokenRanges = [];
for (const heritageClause of classDeclaration.heritageClauses || []) {
if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
extendsTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
if (heritageClause.types.length > 0) {
nodesToCapture.push({ node: heritageClause.types[0], tokenRange: extendsTokenRange });
}
}
else if (heritageClause.token === ts.SyntaxKind.ImplementsKeyword) {
for (const heritageType of heritageClause.types) {
const implementsTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
implementsTokenRanges.push(implementsTokenRange);
nodesToCapture.push({ node: heritageType, tokenRange: implementsTokenRange });
}
}
}
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const isAbstract = (ts.getCombinedModifierFlags(classDeclaration) & ts.ModifierFlags.Abstract) !== 0;
const fileUrlPath = this._getFileUrlPath(classDeclaration);
apiClass = new api_extractor_model_1.ApiClass({
name,
isAbstract,
docComment,
releaseTag,
excerptTokens,
typeParameters,
extendsTokenRange,
implementsTokenRanges,
isExported,
fileUrlPath
});
parentApiItem.addMember(apiClass);
}
this._processChildDeclarations(astDeclaration, Object.assign(Object.assign({}, context), { parentApiItem: apiClass }));
}
_processApiConstructSignature(astDeclaration, context) {
const { parentApiItem } = context;
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
const containerKey = api_extractor_model_1.ApiConstructSignature.getContainerKey(overloadIndex);
let apiConstructSignature = parentApiItem.tryGetMemberByKey(containerKey);
if (apiConstructSignature === undefined) {
const constructSignature = astDeclaration.declaration;
const nodesToCapture = [];
const returnTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: constructSignature.type, tokenRange: returnTypeTokenRange });
const typeParameters = this._captureTypeParameters(nodesToCapture, constructSignature.typeParameters);
const parameters = this._captureParameters(nodesToCapture, constructSignature.parameters);
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const fileUrlPath = this._getFileUrlPath(constructSignature);
apiConstructSignature = new api_extractor_model_1.ApiConstructSignature({
docComment,
releaseTag,
typeParameters,
parameters,
overloadIndex,
excerptTokens,
returnTypeTokenRange,
fileUrlPath
});
parentApiItem.addMember(apiConstructSignature);
}
}
_processApiEnum(astDeclaration, context) {
const { name, isExported, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiEnum.getContainerKey(name);
let apiEnum = parentApiItem.tryGetMemberByKey(containerKey);
if (apiEnum === undefined) {
const excerptTokens = this._buildExcerptTokens(astDeclaration, []);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const preserveMemberOrder = this._collector.extractorConfig.enumMemberOrder === api_extractor_model_1.EnumMemberOrder.Preserve;
const fileUrlPath = this._getFileUrlPath(astDeclaration.declaration);
apiEnum = new api_extractor_model_1.ApiEnum({
name,
docComment,
releaseTag,
excerptTokens,
preserveMemberOrder,
isExported,
fileUrlPath
});
parentApiItem.addMember(apiEnum);
}
this._processChildDeclarations(astDeclaration, Object.assign(Object.assign({}, context), { parentApiItem: apiEnum }));
}
_processApiEnumMember(astDeclaration, context) {
const { name, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiEnumMember.getContainerKey(name);
let apiEnumMember = parentApiItem.tryGetMemberByKey(containerKey);
if (apiEnumMember === undefined) {
const enumMember = astDeclaration.declaration;
const nodesToCapture = [];
let initializerTokenRange = undefined;
if (enumMember.initializer) {
initializerTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: enumMember.initializer, tokenRange: initializerTokenRange });
}
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const fileUrlPath = this._getFileUrlPath(enumMember);
apiEnumMember = new api_extractor_model_1.ApiEnumMember({
name,
docComment,
releaseTag,
excerptTokens,
initializerTokenRange,
fileUrlPath
});
parentApiItem.addMember(apiEnumMember);
}
}
_processApiFunction(astDeclaration, context, altFunctionDeclaration) {
const { name, isExported, parentApiItem } = context;
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
const containerKey = api_extractor_model_1.ApiFunction.getContainerKey(name, overloadIndex);
let apiFunction = parentApiItem.tryGetMemberByKey(containerKey);
if (apiFunction === undefined) {
const functionDeclaration = altFunctionDeclaration !== null && altFunctionDeclaration !== void 0 ? altFunctionDeclaration : astDeclaration.declaration;
const nodesToCapture = [];
const returnTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: functionDeclaration.type, tokenRange: returnTypeTokenRange });
const typeParameters = this._captureTypeParameters(nodesToCapture, functionDeclaration.typeParameters);
const parameters = this._captureParameters(nodesToCapture, functionDeclaration.parameters);
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const fileUrlPath = this._getFileUrlPath(functionDeclaration);
apiFunction = new api_extractor_model_1.ApiFunction({
name,
docComment,
releaseTag,
typeParameters,
parameters,
overloadIndex,
excerptTokens,
returnTypeTokenRange,
isExported,
fileUrlPath
});
parentApiItem.addMember(apiFunction);
}
}
_processApiIndexSignature(astDeclaration, context) {
const { parentApiItem } = context;
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
const containerKey = api_extractor_model_1.ApiIndexSignature.getContainerKey(overloadIndex);
let apiIndexSignature = parentApiItem.tryGetMemberByKey(containerKey);
if (apiIndexSignature === undefined) {
const indexSignature = astDeclaration.declaration;
const nodesToCapture = [];
const returnTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: indexSignature.type, tokenRange: returnTypeTokenRange });
const parameters = this._captureParameters(nodesToCapture, indexSignature.parameters);
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const isReadonly = this._isReadonly(astDeclaration);
const fileUrlPath = this._getFileUrlPath(indexSignature);
apiIndexSignature = new api_extractor_model_1.ApiIndexSignature({
docComment,
releaseTag,
parameters,
overloadIndex,
excerptTokens,
returnTypeTokenRange,
isReadonly,
fileUrlPath
});
parentApiItem.addMember(apiIndexSignature);
}
}
_processApiInterface(astDeclaration, context) {
const { name, isExported, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiInterface.getContainerKey(name);
let apiInterface = parentApiItem.tryGetMemberByKey(containerKey);
if (apiInterface === undefined) {
const interfaceDeclaration = astDeclaration.declaration;
const nodesToCapture = [];
const typeParameters = this._captureTypeParameters(nodesToCapture, interfaceDeclaration.typeParameters);
const extendsTokenRanges = [];
for (const heritageClause of interfaceDeclaration.heritageClauses || []) {
if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
for (const heritageType of heritageClause.types) {
const extendsTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
extendsTokenRanges.push(extendsTokenRange);
nodesToCapture.push({ node: heritageType, tokenRange: extendsTokenRange });
}
}
}
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const fileUrlPath = this._getFileUrlPath(interfaceDeclaration);
apiInterface = new api_extractor_model_1.ApiInterface({
name,
docComment,
releaseTag,
excerptTokens,
typeParameters,
extendsTokenRanges,
isExported,
fileUrlPath
});
parentApiItem.addMember(apiInterface);
}
this._processChildDeclarations(astDeclaration, Object.assign(Object.assign({}, context), { parentApiItem: apiInterface }));
}
_processApiMethod(astDeclaration, context) {
const { name, parentApiItem } = context;
const isStatic = (astDeclaration.modifierFlags & ts.ModifierFlags.Static) !== 0;
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
const containerKey = api_extractor_model_1.ApiMethod.getContainerKey(name, isStatic, overloadIndex);
let apiMethod = parentApiItem.tryGetMemberByKey(containerKey);
if (apiMethod === undefined) {
const methodDeclaration = astDeclaration.declaration;
const nodesToCapture = [];
const returnTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: methodDeclaration.type, tokenRange: returnTypeTokenRange });
const typeParameters = this._captureTypeParameters(nodesToCapture, methodDeclaration.typeParameters);
const parameters = this._captureParameters(nodesToCapture, methodDeclaration.parameters);
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
if (releaseTag === api_extractor_model_1.ReleaseTag.Internal || releaseTag === api_extractor_model_1.ReleaseTag.Alpha) {
return; // trim out items marked as "@internal" or "@alpha"
}
const isOptional = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0;
const isProtected = (astDeclaration.modifierFlags & ts.ModifierFlags.Protected) !== 0;
const isAbstract = (astDeclaration.modifierFlags & ts.ModifierFlags.Abstract) !== 0;
const fileUrlPath = this._getFileUrlPath(methodDeclaration);
apiMethod = new api_extractor_model_1.ApiMethod({
name,
isAbstract,
docComment,
releaseTag,
isProtected,
isStatic,
isOptional,
typeParameters,
parameters,
overloadIndex,
excerptTokens,
returnTypeTokenRange,
fileUrlPath
});
parentApiItem.addMember(apiMethod);
}
}
_processApiMethodSignature(astDeclaration, context) {
const { name, parentApiItem } = context;
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
const containerKey = api_extractor_model_1.ApiMethodSignature.getContainerKey(name, overloadIndex);
let apiMethodSignature = parentApiItem.tryGetMemberByKey(containerKey);
if (apiMethodSignature === undefined) {
const methodSignature = astDeclaration.declaration;
const nodesToCapture = [];
const returnTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: methodSignature.type, tokenRange: returnTypeTokenRange });
const typeParameters = this._captureTypeParameters(nodesToCapture, methodSignature.typeParameters);
const parameters = this._captureParameters(nodesToCapture, methodSignature.parameters);
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const isOptional = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0;
const fileUrlPath = this._getFileUrlPath(methodSignature);
apiMethodSignature = new api_extractor_model_1.ApiMethodSignature({
name,
docComment,
releaseTag,
isOptional,
typeParameters,
parameters,
overloadIndex,
excerptTokens,
returnTypeTokenRange,
fileUrlPath
});
parentApiItem.addMember(apiMethodSignature);
}
}
_processApiNamespace(astDeclaration, context) {
const { name, isExported, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiNamespace.getContainerKey(name);
let apiNamespace = parentApiItem.tryGetMemberByKey(containerKey);
if (apiNamespace === undefined) {
const excerptTokens = this._buildExcerptTokens(astDeclaration, []);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const fileUrlPath = this._getFileUrlPath(astDeclaration.declaration);
apiNamespace = new api_extractor_model_1.ApiNamespace({
name,
docComment,
releaseTag,
excerptTokens,
isExported,
fileUrlPath
});
parentApiItem.addMember(apiNamespace);
}
this._processChildDeclarations(astDeclaration, Object.assign(Object.assign({}, context), { parentApiItem: apiNamespace }));
}
_processApiProperty(astDeclaration, context) {
const { name, parentApiItem } = context;
const isStatic = (astDeclaration.modifierFlags & ts.ModifierFlags.Static) !== 0;
const containerKey = api_extractor_model_1.ApiProperty.getContainerKey(name, isStatic);
let apiProperty = parentApiItem.tryGetMemberByKey(containerKey);
if (apiProperty === undefined) {
const declaration = astDeclaration.declaration;
const nodesToCapture = [];
const propertyTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
let propertyTypeNode;
if (ts.isPropertyDeclaration(declaration) || ts.isGetAccessorDeclaration(declaration)) {
propertyTypeNode = declaration.type;
}
if (ts.isSetAccessorDeclaration(declaration)) {
// Note that TypeScript always reports an error if a setter does not have exactly one parameter.
propertyTypeNode = declaration.parameters[0].type;
}
nodesToCapture.push({ node: propertyTypeNode, tokenRange: propertyTypeTokenRange });
let initializerTokenRange = undefined;
if (ts.isPropertyDeclaration(declaration) && declaration.initializer) {
initializerTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: declaration.initializer, tokenRange: initializerTokenRange });
}
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const isOptional = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0;
const isProtected = (astDeclaration.modifierFlags & ts.ModifierFlags.Protected) !== 0;
const isAbstract = (astDeclaration.modifierFlags & ts.ModifierFlags.Abstract) !== 0;
const isReadonly = this._isReadonly(astDeclaration);
const fileUrlPath = this._getFileUrlPath(declaration);
apiProperty = new api_extractor_model_1.ApiProperty({
name,
docComment,
releaseTag,
isAbstract,
isProtected,
isStatic,
isOptional,
isReadonly,
excerptTokens,
propertyTypeTokenRange,
initializerTokenRange,
fileUrlPath
});
parentApiItem.addMember(apiProperty);
}
else {
// If the property was already declared before (via a merged interface declaration),
// we assume its signature is identical, because the language requires that.
}
}
_processApiPropertySignature(astDeclaration, context) {
const { name, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiPropertySignature.getContainerKey(name);
let apiPropertySignature = parentApiItem.tryGetMemberByKey(containerKey);
if (apiPropertySignature === undefined) {
const propertySignature = astDeclaration.declaration;
const nodesToCapture = [];
const propertyTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: propertySignature.type, tokenRange: propertyTypeTokenRange });
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const isOptional = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0;
const isReadonly = this._isReadonly(astDeclaration);
const fileUrlPath = this._getFileUrlPath(propertySignature);
apiPropertySignature = new api_extractor_model_1.ApiPropertySignature({
name,
docComment,
releaseTag,
isOptional,
excerptTokens,
propertyTypeTokenRange,
isReadonly,
fileUrlPath
});
parentApiItem.addMember(apiPropertySignature);
}
else {
// If the property was already declared before (via a merged interface declaration),
// we assume its signature is identical, because the language requires that.
}
}
_processApiTypeAlias(astDeclaration, context) {
const { name, isExported, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiTypeAlias.getContainerKey(name);
let apiTypeAlias = parentApiItem.tryGetMemberByKey(containerKey);
if (apiTypeAlias === undefined) {
const typeAliasDeclaration = astDeclaration.declaration;
const nodesToCapture = [];
const typeParameters = this._captureTypeParameters(nodesToCapture, typeAliasDeclaration.typeParameters);
const typeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: typeAliasDeclaration.type, tokenRange: typeTokenRange });
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const fileUrlPath = this._getFileUrlPath(typeAliasDeclaration);
apiTypeAlias = new api_extractor_model_1.ApiTypeAlias({
name,
docComment,
typeParameters,
releaseTag,
excerptTokens,
typeTokenRange,
isExported,
fileUrlPath
});
parentApiItem.addMember(apiTypeAlias);
}
}
_processApiVariable(astDeclaration, context) {
const { name, isExported, parentApiItem } = context;
const containerKey = api_extractor_model_1.ApiVariable.getContainerKey(name);
let apiVariable = parentApiItem.tryGetMemberByKey(containerKey);
if (apiVariable === undefined) {
const variableDeclaration = astDeclaration.declaration;
const nodesToCapture = [];
const variableTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: variableDeclaration.type, tokenRange: variableTypeTokenRange });
let initializerTokenRange = undefined;
if (variableDeclaration.initializer) {
initializerTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: variableDeclaration.initializer, tokenRange: initializerTokenRange });
}
const excerptTokens = this._buildExcerptTokens(astDeclaration, nodesToCapture);
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const releaseTag = apiItemMetadata.effectiveReleaseTag;
const isReadonly = this._isReadonly(astDeclaration);
const fileUrlPath = this._getFileUrlPath(variableDeclaration);
apiVariable = new api_extractor_model_1.ApiVariable({
name,
docComment,
releaseTag,
excerptTokens,
variableTypeTokenRange,
initializerTokenRange,
isReadonly,
isExported,
fileUrlPath
});
parentApiItem.addMember(apiVariable);
}
}
/**
* @param nodesToCapture - A list of child nodes whose token ranges we want to capture
*/
_buildExcerptTokens(astDeclaration, nodesToCapture) {
const excerptTokens = [];
// Build the main declaration
ExcerptBuilder_1.ExcerptBuilder.addDeclaration(excerptTokens, astDeclaration, nodesToCapture, this._referenceGenerator);
const declarationMetadata = this._collector.fetchDeclarationMetadata(astDeclaration);
// Add any ancillary declarations
for (const ancillaryDeclaration of declarationMetadata.ancillaryDeclarations) {
ExcerptBuilder_1.ExcerptBuilder.addBlankLine(excerptTokens);
ExcerptBuilder_1.ExcerptBuilder.addDeclaration(excerptTokens, ancillaryDeclaration, nodesToCapture, this._referenceGenerator);
}
return excerptTokens;
}
_captureTypeParameters(nodesToCapture, typeParameterNodes) {
const typeParameters = [];
if (typeParameterNodes) {
for (const typeParameter of typeParameterNodes) {
const constraintTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: typeParameter.constraint, tokenRange: constraintTokenRange });
const defaultTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: typeParameter.default, tokenRange: defaultTypeTokenRange });
typeParameters.push({
typeParameterName: typeParameter.name.getText().trim(),
constraintTokenRange,
defaultTypeTokenRange
});
}
}
return typeParameters;
}
_captureParameters(nodesToCapture, parameterNodes) {
const parameters = [];
for (const parameter of parameterNodes) {
const parameterTypeTokenRange = ExcerptBuilder_1.ExcerptBuilder.createEmptyTokenRange();
nodesToCapture.push({ node: parameter.type, tokenRange: parameterTypeTokenRange });
parameters.push({
parameterName: parameter.name.getText().trim(),
parameterTypeTokenRange,
isOptional: this._collector.typeChecker.isOptionalParameter(parameter)
});
}
return parameters;
}
_isReadonly(astDeclaration) {
var _a;
switch (astDeclaration.declaration.kind) {
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.IndexSignature:
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.SetAccessor:
case ts.SyntaxKind.VariableDeclaration: {
const apiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
const docComment = apiItemMetadata.tsdocComment;
const declarationMetadata = this._collector.fetchDeclarationMetadata(astDeclaration);
const hasReadonlyModifier = (astDeclaration.modifierFlags & ts.ModifierFlags.Readonly) !== 0;
const hasReadonlyDocTag = !!((_a = docComment === null || docComment === void 0 ? void 0 : docComment.modifierTagSet) === null || _a === void 0 ? void 0 : _a.hasTagName('@readonly'));
const isGetterWithNoSetter = ts.isGetAccessorDeclaration(astDeclaration.declaration) &&
declarationMetadata.ancillaryDeclarations.length === 0;
const isVarConst = ts.isVariableDeclaration(astDeclaration.declaration) &&
TypeScriptInternals_1.TypeScriptInternals.isVarConst(astDeclaration.declaration);
return hasReadonlyModifier || hasReadonlyDocTag || isGetterWithNoSetter || isVarConst;
}
default: {
// Readonly-ness does not make sense for any other declaration kind.
return false;
}
}
}
_getFileUrlPath(declaration) {
const sourceFile = declaration.getSourceFile();
const sourceLocation = this._collector.sourceMapper.getSourceLocation({
sourceFile,
pos: declaration.pos
});
let result = path.relative(this._collector.extractorConfig.projectFolder, sourceLocation.sourceFilePath);
result = node_core_library_1.Path.convertToSlashes(result);
return result;
}
}
exports.ApiModelGenerator = ApiModelGenerator;
//# sourceMappingURL=ApiModelGenerator.js.map