293 lines
16 KiB
JavaScript
293 lines
16 KiB
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 (mod) {
|
||
|
if (mod && mod.__esModule) return mod;
|
||
|
var result = {};
|
||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
|
__setModuleDefault(result, mod);
|
||
|
return result;
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.Extractor = exports.ExtractorResult = void 0;
|
||
|
const path = __importStar(require("path"));
|
||
|
const semver = __importStar(require("semver"));
|
||
|
const ts = __importStar(require("typescript"));
|
||
|
const resolve = __importStar(require("resolve"));
|
||
|
const node_core_library_1 = require("@rushstack/node-core-library");
|
||
|
const ExtractorConfig_1 = require("./ExtractorConfig");
|
||
|
const Collector_1 = require("../collector/Collector");
|
||
|
const DtsRollupGenerator_1 = require("../generators/DtsRollupGenerator");
|
||
|
const ApiModelGenerator_1 = require("../generators/ApiModelGenerator");
|
||
|
const ApiReportGenerator_1 = require("../generators/ApiReportGenerator");
|
||
|
const PackageMetadataManager_1 = require("../analyzer/PackageMetadataManager");
|
||
|
const ValidationEnhancer_1 = require("../enhancers/ValidationEnhancer");
|
||
|
const DocCommentEnhancer_1 = require("../enhancers/DocCommentEnhancer");
|
||
|
const CompilerState_1 = require("./CompilerState");
|
||
|
const MessageRouter_1 = require("../collector/MessageRouter");
|
||
|
const tsdoc_config_1 = require("@microsoft/tsdoc-config");
|
||
|
const SourceMapper_1 = require("../collector/SourceMapper");
|
||
|
/**
|
||
|
* This object represents the outcome of an invocation of API Extractor.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class ExtractorResult {
|
||
|
/** @internal */
|
||
|
constructor(properties) {
|
||
|
this.compilerState = properties.compilerState;
|
||
|
this.extractorConfig = properties.extractorConfig;
|
||
|
this.succeeded = properties.succeeded;
|
||
|
this.apiReportChanged = properties.apiReportChanged;
|
||
|
this.errorCount = properties.errorCount;
|
||
|
this.warningCount = properties.warningCount;
|
||
|
}
|
||
|
}
|
||
|
exports.ExtractorResult = ExtractorResult;
|
||
|
/**
|
||
|
* The starting point for invoking the API Extractor tool.
|
||
|
* @public
|
||
|
*/
|
||
|
class Extractor {
|
||
|
/**
|
||
|
* Returns the version number of the API Extractor NPM package.
|
||
|
*/
|
||
|
static get version() {
|
||
|
return Extractor._getPackageJson().version;
|
||
|
}
|
||
|
/**
|
||
|
* Returns the package name of the API Extractor NPM package.
|
||
|
*/
|
||
|
static get packageName() {
|
||
|
return Extractor._getPackageJson().name;
|
||
|
}
|
||
|
static _getPackageJson() {
|
||
|
return node_core_library_1.PackageJsonLookup.loadOwnPackageJson(__dirname);
|
||
|
}
|
||
|
/**
|
||
|
* Load the api-extractor.json config file from the specified path, and then invoke API Extractor.
|
||
|
*/
|
||
|
static loadConfigAndInvoke(configFilePath, options) {
|
||
|
const extractorConfig = ExtractorConfig_1.ExtractorConfig.loadFileAndPrepare(configFilePath);
|
||
|
return Extractor.invoke(extractorConfig, options);
|
||
|
}
|
||
|
/**
|
||
|
* Invoke API Extractor using an already prepared `ExtractorConfig` object.
|
||
|
*/
|
||
|
static invoke(extractorConfig, options) {
|
||
|
if (!options) {
|
||
|
options = {};
|
||
|
}
|
||
|
const localBuild = options.localBuild || false;
|
||
|
let compilerState;
|
||
|
if (options.compilerState) {
|
||
|
compilerState = options.compilerState;
|
||
|
}
|
||
|
else {
|
||
|
compilerState = CompilerState_1.CompilerState.create(extractorConfig, options);
|
||
|
}
|
||
|
const sourceMapper = new SourceMapper_1.SourceMapper();
|
||
|
const messageRouter = new MessageRouter_1.MessageRouter({
|
||
|
workingPackageFolder: extractorConfig.packageFolder,
|
||
|
messageCallback: options.messageCallback,
|
||
|
messagesConfig: extractorConfig.messages || {},
|
||
|
showVerboseMessages: !!options.showVerboseMessages,
|
||
|
showDiagnostics: !!options.showDiagnostics,
|
||
|
tsdocConfiguration: extractorConfig.tsdocConfiguration,
|
||
|
sourceMapper
|
||
|
});
|
||
|
if (extractorConfig.tsdocConfigFile.filePath && !extractorConfig.tsdocConfigFile.fileNotFound) {
|
||
|
if (!node_core_library_1.Path.isEqual(extractorConfig.tsdocConfigFile.filePath, ExtractorConfig_1.ExtractorConfig._tsdocBaseFilePath)) {
|
||
|
messageRouter.logVerbose("console-using-custom-tsdoc-config" /* ConsoleMessageId.UsingCustomTSDocConfig */, 'Using custom TSDoc config from ' + extractorConfig.tsdocConfigFile.filePath);
|
||
|
}
|
||
|
}
|
||
|
this._checkCompilerCompatibility(extractorConfig, messageRouter);
|
||
|
if (messageRouter.showDiagnostics) {
|
||
|
messageRouter.logDiagnostic('');
|
||
|
messageRouter.logDiagnosticHeader('Final prepared ExtractorConfig');
|
||
|
messageRouter.logDiagnostic(extractorConfig.getDiagnosticDump());
|
||
|
messageRouter.logDiagnosticFooter();
|
||
|
messageRouter.logDiagnosticHeader('Compiler options');
|
||
|
const serializedCompilerOptions = MessageRouter_1.MessageRouter.buildJsonDumpObject(compilerState.program.getCompilerOptions());
|
||
|
messageRouter.logDiagnostic(JSON.stringify(serializedCompilerOptions, undefined, 2));
|
||
|
messageRouter.logDiagnosticFooter();
|
||
|
messageRouter.logDiagnosticHeader('TSDoc configuration');
|
||
|
// Convert the TSDocConfiguration into a tsdoc.json representation
|
||
|
const combinedConfigFile = tsdoc_config_1.TSDocConfigFile.loadFromParser(extractorConfig.tsdocConfiguration);
|
||
|
const serializedTSDocConfig = MessageRouter_1.MessageRouter.buildJsonDumpObject(combinedConfigFile.saveToObject());
|
||
|
messageRouter.logDiagnostic(JSON.stringify(serializedTSDocConfig, undefined, 2));
|
||
|
messageRouter.logDiagnosticFooter();
|
||
|
}
|
||
|
const collector = new Collector_1.Collector({
|
||
|
program: compilerState.program,
|
||
|
messageRouter,
|
||
|
extractorConfig: extractorConfig,
|
||
|
sourceMapper
|
||
|
});
|
||
|
collector.analyze();
|
||
|
DocCommentEnhancer_1.DocCommentEnhancer.analyze(collector);
|
||
|
ValidationEnhancer_1.ValidationEnhancer.analyze(collector);
|
||
|
const modelBuilder = new ApiModelGenerator_1.ApiModelGenerator(collector);
|
||
|
const apiPackage = modelBuilder.buildApiPackage();
|
||
|
if (messageRouter.showDiagnostics) {
|
||
|
messageRouter.logDiagnostic(''); // skip a line after any diagnostic messages
|
||
|
}
|
||
|
if (extractorConfig.docModelEnabled) {
|
||
|
messageRouter.logVerbose("console-writing-doc-model-file" /* ConsoleMessageId.WritingDocModelFile */, 'Writing: ' + extractorConfig.apiJsonFilePath);
|
||
|
apiPackage.saveToJsonFile(extractorConfig.apiJsonFilePath, {
|
||
|
toolPackage: Extractor.packageName,
|
||
|
toolVersion: Extractor.version,
|
||
|
newlineConversion: extractorConfig.newlineKind,
|
||
|
ensureFolderExists: true,
|
||
|
testMode: extractorConfig.testMode
|
||
|
});
|
||
|
}
|
||
|
let apiReportChanged = false;
|
||
|
if (extractorConfig.apiReportEnabled) {
|
||
|
const actualApiReportPath = extractorConfig.reportTempFilePath;
|
||
|
const actualApiReportShortPath = extractorConfig._getShortFilePath(extractorConfig.reportTempFilePath);
|
||
|
const expectedApiReportPath = extractorConfig.reportFilePath;
|
||
|
const expectedApiReportShortPath = extractorConfig._getShortFilePath(extractorConfig.reportFilePath);
|
||
|
const actualApiReportContent = ApiReportGenerator_1.ApiReportGenerator.generateReviewFileContent(collector);
|
||
|
// Write the actual file
|
||
|
node_core_library_1.FileSystem.writeFile(actualApiReportPath, actualApiReportContent, {
|
||
|
ensureFolderExists: true,
|
||
|
convertLineEndings: extractorConfig.newlineKind
|
||
|
});
|
||
|
// Compare it against the expected file
|
||
|
if (node_core_library_1.FileSystem.exists(expectedApiReportPath)) {
|
||
|
const expectedApiReportContent = node_core_library_1.FileSystem.readFile(expectedApiReportPath);
|
||
|
if (!ApiReportGenerator_1.ApiReportGenerator.areEquivalentApiFileContents(actualApiReportContent, expectedApiReportContent)) {
|
||
|
apiReportChanged = true;
|
||
|
if (!localBuild) {
|
||
|
// For a production build, issue a warning that will break the CI build.
|
||
|
messageRouter.logWarning("console-api-report-not-copied" /* ConsoleMessageId.ApiReportNotCopied */, 'You have changed the public API signature for this project.' +
|
||
|
` Please copy the file "${actualApiReportShortPath}" to "${expectedApiReportShortPath}",` +
|
||
|
` or perform a local build (which does this automatically).` +
|
||
|
` See the Git repo documentation for more info.`);
|
||
|
}
|
||
|
else {
|
||
|
// For a local build, just copy the file automatically.
|
||
|
messageRouter.logWarning("console-api-report-copied" /* ConsoleMessageId.ApiReportCopied */, 'You have changed the public API signature for this project.' +
|
||
|
` Updating ${expectedApiReportShortPath}`);
|
||
|
node_core_library_1.FileSystem.writeFile(expectedApiReportPath, actualApiReportContent, {
|
||
|
ensureFolderExists: true,
|
||
|
convertLineEndings: extractorConfig.newlineKind
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
messageRouter.logVerbose("console-api-report-unchanged" /* ConsoleMessageId.ApiReportUnchanged */, `The API report is up to date: ${actualApiReportShortPath}`);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// The target file does not exist, so we are setting up the API review file for the first time.
|
||
|
//
|
||
|
// NOTE: People sometimes make a mistake where they move a project and forget to update the "reportFolder"
|
||
|
// setting, which causes a new file to silently get written to the wrong place. This can be confusing.
|
||
|
// Thus we treat the initial creation of the file specially.
|
||
|
apiReportChanged = true;
|
||
|
if (!localBuild) {
|
||
|
// For a production build, issue a warning that will break the CI build.
|
||
|
messageRouter.logWarning("console-api-report-not-copied" /* ConsoleMessageId.ApiReportNotCopied */, 'The API report file is missing.' +
|
||
|
` Please copy the file "${actualApiReportShortPath}" to "${expectedApiReportShortPath}",` +
|
||
|
` or perform a local build (which does this automatically).` +
|
||
|
` See the Git repo documentation for more info.`);
|
||
|
}
|
||
|
else {
|
||
|
const expectedApiReportFolder = path.dirname(expectedApiReportPath);
|
||
|
if (!node_core_library_1.FileSystem.exists(expectedApiReportFolder)) {
|
||
|
messageRouter.logError("console-api-report-folder-missing" /* ConsoleMessageId.ApiReportFolderMissing */, 'Unable to create the API report file. Please make sure the target folder exists:\n' +
|
||
|
expectedApiReportFolder);
|
||
|
}
|
||
|
else {
|
||
|
node_core_library_1.FileSystem.writeFile(expectedApiReportPath, actualApiReportContent, {
|
||
|
convertLineEndings: extractorConfig.newlineKind
|
||
|
});
|
||
|
messageRouter.logWarning("console-api-report-created" /* ConsoleMessageId.ApiReportCreated */, 'The API report file was missing, so a new file was created. Please add this file to Git:\n' +
|
||
|
expectedApiReportPath);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (extractorConfig.rollupEnabled) {
|
||
|
Extractor._generateRollupDtsFile(collector, extractorConfig.publicTrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.PublicRelease, extractorConfig.newlineKind);
|
||
|
Extractor._generateRollupDtsFile(collector, extractorConfig.alphaTrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.AlphaRelease, extractorConfig.newlineKind);
|
||
|
Extractor._generateRollupDtsFile(collector, extractorConfig.betaTrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.BetaRelease, extractorConfig.newlineKind);
|
||
|
Extractor._generateRollupDtsFile(collector, extractorConfig.untrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.InternalRelease, extractorConfig.newlineKind);
|
||
|
}
|
||
|
if (extractorConfig.tsdocMetadataEnabled) {
|
||
|
// Write the tsdoc-metadata.json file for this project
|
||
|
PackageMetadataManager_1.PackageMetadataManager.writeTsdocMetadataFile(extractorConfig.tsdocMetadataFilePath, extractorConfig.newlineKind);
|
||
|
}
|
||
|
// Show all the messages that we collected during analysis
|
||
|
messageRouter.handleRemainingNonConsoleMessages();
|
||
|
// Determine success
|
||
|
let succeeded;
|
||
|
if (localBuild) {
|
||
|
// For a local build, fail if there were errors (but ignore warnings)
|
||
|
succeeded = messageRouter.errorCount === 0;
|
||
|
}
|
||
|
else {
|
||
|
// For a production build, fail if there were any errors or warnings
|
||
|
succeeded = messageRouter.errorCount + messageRouter.warningCount === 0;
|
||
|
}
|
||
|
return new ExtractorResult({
|
||
|
compilerState,
|
||
|
extractorConfig,
|
||
|
succeeded,
|
||
|
apiReportChanged,
|
||
|
errorCount: messageRouter.errorCount,
|
||
|
warningCount: messageRouter.warningCount
|
||
|
});
|
||
|
}
|
||
|
static _checkCompilerCompatibility(extractorConfig, messageRouter) {
|
||
|
messageRouter.logInfo("console-preamble" /* ConsoleMessageId.Preamble */, `Analysis will use the bundled TypeScript version ${ts.version}`);
|
||
|
try {
|
||
|
const typescriptPath = resolve.sync('typescript', {
|
||
|
basedir: extractorConfig.projectFolder,
|
||
|
preserveSymlinks: false
|
||
|
});
|
||
|
const packageJsonLookup = new node_core_library_1.PackageJsonLookup();
|
||
|
const packageJson = packageJsonLookup.tryLoadNodePackageJsonFor(typescriptPath);
|
||
|
if (packageJson && packageJson.version && semver.valid(packageJson.version)) {
|
||
|
// Consider a newer MINOR release to be incompatible
|
||
|
const ourMajor = semver.major(ts.version);
|
||
|
const ourMinor = semver.minor(ts.version);
|
||
|
const theirMajor = semver.major(packageJson.version);
|
||
|
const theirMinor = semver.minor(packageJson.version);
|
||
|
if (theirMajor > ourMajor || (theirMajor === ourMajor && theirMinor > ourMinor)) {
|
||
|
messageRouter.logInfo("console-compiler-version-notice" /* ConsoleMessageId.CompilerVersionNotice */, `*** The target project appears to use TypeScript ${packageJson.version} which is newer than the` +
|
||
|
` bundled compiler engine; consider upgrading API Extractor.`);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (e) {
|
||
|
// The compiler detection heuristic is not expected to work in many configurations
|
||
|
}
|
||
|
}
|
||
|
static _generateRollupDtsFile(collector, outputPath, dtsKind, newlineKind) {
|
||
|
if (outputPath !== '') {
|
||
|
collector.messageRouter.logVerbose("console-writing-dts-rollup" /* ConsoleMessageId.WritingDtsRollup */, `Writing package typings: ${outputPath}`);
|
||
|
DtsRollupGenerator_1.DtsRollupGenerator.writeTypingsFile(collector, outputPath, dtsKind, newlineKind);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exports.Extractor = Extractor;
|
||
|
//# sourceMappingURL=Extractor.js.map
|