@microsoft/api-extractor
Version:
Validate, document, and review the exported API for a TypeScript library
118 lines (116 loc) • 5.91 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.
Object.defineProperty(exports, "__esModule", { value: true });
/* tslint:disable:no-bitwise */
const ts = require("typescript");
const AstModuleVariable_1 = require("./AstModuleVariable");
const AstItem_1 = require("./AstItem");
const AstModule_1 = require("./AstModule");
const allowedTypes = ['string', 'number', 'boolean'];
/**
* This class is part of the AstItem abstract syntax tree. It represents exports of
* a namespace, the exports can be module variable constants of type "string", "boolean" or "number".
* An AstNamespace is defined using TypeScript's "namespace" keyword.
*
* @remarks A note about terminology:
* - EcmaScript "namespace modules" are not conventional namespaces; their semantics are
* more like static classes in C# or Java.
* - API Extractor's support for namespaces is currently limited to representing tables of
* constants, and has a benefit of enabling WebPack to avoid bundling unused values.
* - We currently still recommend to use static classes for utility libraries, since this
* provides getters/setters, public/private, and some other structure missing from namespaces.
*/
class AstNamespace extends AstModule_1.default {
constructor(options) {
super(options);
this._exportedNormalizedSymbols = [];
this.kind = AstItem_1.AstItemKind.Namespace;
this.name = options.declarationSymbol.name;
const exportSymbols = this.typeChecker.getExportsOfModule(this.declarationSymbol);
if (exportSymbols) {
if (this.context.policies.namespaceSupport === 'conservative') {
this._processConservativeMembers(exportSymbols);
}
else {
this._processPermissiveMembers(exportSymbols);
}
}
}
// Used when policies.namespaceSupport=conservative
_processConservativeMembers(exportSymbols) {
for (const exportSymbol of exportSymbols) {
const followedSymbol = this.followAliases(exportSymbol);
if (!followedSymbol.declarations) {
// This is an API Extractor bug, but it could happen e.g. if we upgrade to a new
// version of the TypeScript compiler that introduces new AST variations that we
// haven't tested before.
this.reportWarning(`The definition "${exportSymbol.name}" has no declarations`);
continue;
}
if (!(followedSymbol.flags === ts.SymbolFlags.BlockScopedVariable)) {
this.reportWarning(`Unsupported export "${exportSymbol.name}" ` +
'Currently the "namespace" block only supports constant variables.');
continue;
}
// Since we are imposing that the items within a namespace be
// const properties we are only taking the first declaration.
// If we decide to add support for other types within a namespace
// we will have for evaluate each declaration.
const declarations = followedSymbol.getDeclarations();
if (!declarations) {
throw new Error('Missing declaration');
}
const declaration = declarations[0];
if (declaration.parent && declaration.parent.flags !== ts.NodeFlags.Const) {
this.reportWarning(`Export "${exportSymbol.name}" is missing the "const" ` +
'modifier. Currently the "namespace" block only supports constant variables.');
continue;
}
const propertySignature = declaration;
if (!propertySignature.type || allowedTypes.indexOf(propertySignature.type.getText()) < 0) {
this.reportWarning(`Export "${exportSymbol.name}" must specify and be of type` +
'"string", "number" or "boolean"');
continue;
}
if (!propertySignature.initializer) {
this.reportWarning(`Export "${exportSymbol.name}" must have an initialized value`);
continue;
}
// Typescript's VariableDeclaration AST nodes have an VariableDeclarationList parent,
// and the VariableDeclarationList exists within a VariableStatement, which is where
// the JSDoc comment Node can be found.
// If there is no parent or grandparent of this VariableDeclaration then
// we do not know how to obtain the JSDoc comment.
let jsdocNode = undefined;
if (!declaration.parent || !declaration.parent.parent ||
declaration.parent.parent.kind !== ts.SyntaxKind.VariableStatement) {
this.reportWarning(`Unable to locate the documentation node for "${exportSymbol.name}"; `
+ `this may be an API Extractor bug`);
}
else {
jsdocNode = declaration.parent.parent;
}
const exportMemberOptions = {
context: this.context,
declaration,
declarationSymbol: followedSymbol,
jsdocNode: jsdocNode,
exportSymbol
};
this.addMemberItem(new AstModuleVariable_1.default(exportMemberOptions));
this._exportedNormalizedSymbols.push({
exportedName: exportSymbol.name,
followedSymbol: followedSymbol
});
}
}
// Used when policies.namespaceSupport=permissive
_processPermissiveMembers(exportSymbols) {
for (const exportSymbol of exportSymbols) {
this.processModuleExport(exportSymbol);
}
}
}
exports.default = AstNamespace;
//# sourceMappingURL=AstNamespace.js.map