@microsoft/api-extractor
Version:
Validate, document, and review the exported API for a TypeScript library
117 lines • 6.23 kB
JavaScript
;
// 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 AstItem_1 = require("./AstItem");
const AstModule_1 = require("./AstModule");
const TypeScriptHelpers_1 = require("../utils/TypeScriptHelpers");
/**
* This class is part of the AstItem abstract syntax tree. It represents the top-level
* exports for an Rush package. This object acts as the root of the Extractor's tree.
*/
class AstPackage extends AstModule_1.AstModule {
constructor(context, rootFile) {
super(AstPackage._getOptions(context, rootFile));
this._exportedNormalizedSymbols = [];
this.kind = AstItem_1.AstItemKind.Package;
// The scoped package name. (E.g. "@microsoft/api-extractor")
this.name = context.packageName;
const exportSymbols = this.typeChecker.getExportsOfModule(this.declarationSymbol) || [];
for (const exportSymbol of exportSymbols) {
this.processModuleExport(exportSymbol);
const followedSymbol = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(exportSymbol, this.typeChecker);
this._exportedNormalizedSymbols.push({
exportedName: exportSymbol.name,
followedSymbol: followedSymbol
});
}
}
static _getOptions(context, rootFile) {
const rootFileSymbol = TypeScriptHelpers_1.TypeScriptHelpers.tryGetSymbolForDeclaration(rootFile);
if (!rootFileSymbol) {
throw new Error('The entry point file does not appear to have any exports:\n' + rootFile.fileName
+ '\nNote that API Extractor does not yet support libraries consisting entirely of ambient types.');
}
if (!rootFileSymbol.declarations) {
throw new Error('Unable to find a root declaration for this package');
}
// The @packagedocumentation comment is special because it is not attached to an AST
// definition. Instead, it is part of the "trivia" tokens that the compiler treats
// as irrelevant white space.
//
// WARNING: If the comment doesn't precede an export statement, the compiler will omit
// it from the *.d.ts file, and API Extractor won't find it. If this happens, you need
// to rearrange your statements to ensure it is passed through.
//
// This implementation assumes that the "@packagedocumentation" will be in the first JSDoc-comment
// that appears in the entry point *.d.ts file. We could possibly look in other places,
// but the above warning suggests enforcing a standardized layout. This design choice is open
// to feedback.
let packageCommentRange = undefined; // empty string
for (const commentRange of ts.getLeadingCommentRanges(rootFile.text, rootFile.getFullStart()) || []) {
if (commentRange.kind === ts.SyntaxKind.MultiLineCommentTrivia) {
const commentBody = rootFile.text.substring(commentRange.pos, commentRange.end);
// Choose the first JSDoc-style comment
if (/^\s*\/\*\*/.test(commentBody)) {
// But onliy if it looks like it's trying to be @packagedocumentation
// (The ApiDocumentation parser will validate this more rigorously)
if (commentBody.indexOf('@packagedocumentation') >= 0) {
packageCommentRange = commentRange;
}
break;
}
}
}
if (!packageCommentRange) {
// If we didn't find the @packagedocumentation tag in the expected place, is it in some
// wrong place? This sanity check helps people to figure out why there comment isn't working.
for (const statement of rootFile.statements) {
const ranges = [];
ranges.push(...ts.getLeadingCommentRanges(rootFile.text, statement.getFullStart()) || []);
ranges.push(...ts.getTrailingCommentRanges(rootFile.text, statement.getEnd()) || []);
for (const commentRange of ranges) {
const commentBody = rootFile.text.substring(commentRange.pos, commentRange.end);
if (commentBody.indexOf('@packagedocumentation') >= 0) {
context.reportError('The @packagedocumentation comment must appear at the top of entry point *.d.ts file', rootFile, commentRange.pos);
}
}
}
}
return {
context,
declaration: rootFileSymbol.declarations[0],
declarationSymbol: rootFileSymbol,
// NOTE: If there is no range, then provide an empty range to prevent ApiItem from
// looking in the default place
aedocCommentRange: packageCommentRange || { pos: 0, end: 0 }
};
}
/**
* Finds and returns the original symbol name.
*
* For example, suppose a class is defined as "export default class MyClass { }"
* but exported from the package's index.ts like this:
*
* export { default as _MyClass } from './MyClass';
*
* In this example, given the symbol for _MyClass, getExportedSymbolName() will return
* the string "MyClass".
*/
tryGetExportedSymbolName(symbol) {
const followedSymbol = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(symbol, this.typeChecker);
for (const exportedSymbol of this._exportedNormalizedSymbols) {
if (exportedSymbol.followedSymbol === followedSymbol) {
return exportedSymbol.exportedName;
}
}
return undefined;
}
shouldHaveDocumentation() {
// We don't write JSDoc for the AstPackage object
return false;
}
}
exports.AstPackage = AstPackage;
//# sourceMappingURL=AstPackage.js.map