Skip to content

Commit

Permalink
Merge pull request #1211 from Microsoft/octogonz/ae-remove-logger
Browse files Browse the repository at this point in the history
[api-extractor] Remove ILogger and instead expose console output as ExtractorMessage objects
  • Loading branch information
octogonz authored Apr 6, 2019
2 parents 304f4ed + 5f54036 commit 4c07317
Show file tree
Hide file tree
Showing 18 changed files with 469 additions and 271 deletions.
6 changes: 3 additions & 3 deletions apps/api-extractor/src/analyzer/AstSymbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { TypeScriptHelpers } from './TypeScriptHelpers';
import { AstSymbol } from './AstSymbol';
import { AstModule, AstModuleExportInfo } from './AstModule';
import { PackageMetadataManager } from './PackageMetadataManager';
import { ILogger } from '../api/ILogger';
import { ExportAnalyzer } from './ExportAnalyzer';
import { AstImport } from './AstImport';
import { MessageRouter } from '../collector/MessageRouter';

export type AstEntity = AstSymbol | AstImport;

Expand Down Expand Up @@ -83,11 +83,11 @@ export class AstSymbolTable {
= new Map<ts.Identifier, AstEntity | undefined>();

public constructor(program: ts.Program, typeChecker: ts.TypeChecker, packageJsonLookup: PackageJsonLookup,
logger: ILogger) {
messageRouter: MessageRouter) {

this._program = program;
this._typeChecker = typeChecker;
this._packageMetadataManager = new PackageMetadataManager(packageJsonLookup, logger);
this._packageMetadataManager = new PackageMetadataManager(packageJsonLookup, messageRouter);

this._exportAnalyzer = new ExportAnalyzer(
this._program,
Expand Down
11 changes: 6 additions & 5 deletions apps/api-extractor/src/analyzer/PackageMetadataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
INodePackageJson
} from '@microsoft/node-core-library';
import { Extractor } from '../api/Extractor';
import { ILogger } from '../api/ILogger';
import { MessageRouter } from '../collector/MessageRouter';
import { ConsoleMessageId } from '../api/ConsoleMessageId';

/**
* Represents analyzed information for a package.json file.
Expand Down Expand Up @@ -57,7 +58,7 @@ export class PackageMetadataManager {
public static tsdocMetadataFilename: string = 'tsdoc-metadata.json';

private readonly _packageJsonLookup: PackageJsonLookup;
private readonly _logger: ILogger;
private readonly _messageRouter: MessageRouter;
private readonly _packageMetadataByPackageJsonPath: Map<string, PackageMetadata>
= new Map<string, PackageMetadata>();

Expand Down Expand Up @@ -147,9 +148,9 @@ export class PackageMetadataManager {
});
}

public constructor(packageJsonLookup: PackageJsonLookup, logger: ILogger) {
public constructor(packageJsonLookup: PackageJsonLookup, messageRouter: MessageRouter) {
this._packageJsonLookup = packageJsonLookup;
this._logger = logger;
this._messageRouter = messageRouter;
}

/**
Expand Down Expand Up @@ -179,7 +180,7 @@ export class PackageMetadataManager {
);

if (FileSystem.exists(tsdocMetadataPath)) {
this._logger.logVerbose('Found metadata in ' + tsdocMetadataPath);
this._messageRouter.logVerbose(ConsoleMessageId.FoundTSDocMetadata, 'Found metadata in ' + tsdocMetadataPath);
// If the file exists at all, assume it was written by API Extractor
aedocSupported = true;
}
Expand Down
50 changes: 50 additions & 0 deletions apps/api-extractor/src/api/ConsoleMessageId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

/**
* Unique identifiers for console messages reported by API Extractor.
*
* @remarks
*
* These strings are possible values for the {@link ExtractorMessage.messageId} property
* when the `ExtractorMessage.category` is {@link ExtractorMessageCategory.Console}.
*
* @public
*/
export const enum ConsoleMessageId {
/**
* "'Found metadata in ___"
*/
FoundTSDocMetadata = 'console-found-tsdoc-metadata',

/**
* "Writing: ___"
*/
WritingDocModelFile = 'console-writing-doc-model-file',

/**
* "Writing package typings: ___"
*/
WritingDtsRollup = 'console-writing-dts-rollup',

/**
* "You have changed the public API signature for this project. Please overwrite ___ with a
* copy of ___ and then request an API review. See the Git repository README.md for more info."
*/
ApiReportNotCopied = 'console-api-report-not-copied',

/**
* "You have changed the public API signature for this project. Updating ___"
*/
ApiReportCopied = 'console-api-report-copied',

/**
* "The API signature is up to date: ___"
*/
ApiReportUnchanged = 'console-api-report-unchanged',

/**
* "The API review file has not been set up. Do this by copying ___ to ___ and committing it."
*/
ApiReportMissing = 'console-api-report-missing'
}
117 changes: 60 additions & 57 deletions apps/api-extractor/src/api/Extractor.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import lodash = require('lodash');
import colors = require('colors');

import {
FileSystem,
NewlineKind,
PackageJsonLookup,
IPackageJson
} from '@microsoft/node-core-library';
import { ExtractorConfig } from './ExtractorConfig';
import { ILogger } from './ILogger';
import { Collector } from '../collector/Collector';
import { DtsRollupGenerator, DtsRollupKind } from '../generators/DtsRollupGenerator';
import { MonitoredLogger } from './MonitoredLogger';
import { ApiModelGenerator } from '../generators/ApiModelGenerator';
import { ApiPackage } from '@microsoft/api-extractor-model';
import { ReviewFileGenerator } from '../generators/ReviewFileGenerator';
import { PackageMetadataManager } from '../analyzer/PackageMetadataManager';
import { ValidationEnhancer } from '../enhancers/ValidationEnhancer';
import { DocCommentEnhancer } from '../enhancers/DocCommentEnhancer';
import { CompilerState } from './CompilerState';
import { ExtractorMessage } from './ExtractorMessage';
import { MessageRouter } from '../collector/MessageRouter';
import { ConsoleMessageId } from './ConsoleMessageId';

/**
* Runtime options for Extractor.
Expand All @@ -35,12 +33,6 @@ export interface IExtractorInvokeOptions {
*/
compilerState?: CompilerState;

/**
* Allows the caller to customize how API Extractor's errors, warnings, and informational logging is processed.
* If omitted, the output will be printed to the console.
*/
customLogger?: Partial<ILogger>;

/**
* Indicates that API Extractor is running as part of a local build, e.g. on developer's
* machine. This disables certain validation that would normally be performed
Expand All @@ -51,12 +43,27 @@ export interface IExtractorInvokeOptions {
*/
localBuild?: boolean;

/**
* If true, API Extractor will include {@link ExtractorLogLevel.Verbose} messages in its output.
*/
showVerboseMessages?: boolean;

/**
* By default API Extractor uses its own TypeScript compiler version to analyze your project.
* This can often cause compiler errors due to incompatibilities between different TS versions.
* Use this option to specify the folder path for your compiler version.
*/
typescriptCompilerFolder?: string;

/**
* An optional callback function that will be called for each `ExtractorMessage` before it is displayed by
* API Extractor. The callback can customize the message, handle it, or discard it.
*
* @remarks
* If a `messageCallback` is not provided, then by default API Extractor will print the messages to
* the STDERR/STDOUT console.
*/
messageCallback?: (message: ExtractorMessage) => void;
}

/**
Expand Down Expand Up @@ -88,12 +95,23 @@ export class ExtractorResult {
public readonly succeeded: boolean;

/**
* Reports the number of times that {@link ILogger.logError} was called.
* Returns true if the API report was found to have changed.
*/
public readonly apiReportChanged: boolean;

/**
* Reports the number of errors encountered during analysis.
*
* @remarks
* This does not count exceptions, where unexpected issues prematurely abort the operation.
*/
public readonly errorCount: number;

/**
* Reports the number of times that {@link ILogger.logWarning} was called.
* Reports the number of warnings encountered during analysis.
*
* @remarks
* This does not count warnings that are emitted in the API report file.
*/
public readonly warningCount: number;

Expand All @@ -102,6 +120,7 @@ export class ExtractorResult {
this.compilerState = properties.compilerState;
this.extractorConfig = properties.extractorConfig;
this.succeeded = properties.succeeded;
this.apiReportChanged = properties.apiReportChanged;
this.errorCount = properties.errorCount;
this.warningCount = properties.warningCount;
}
Expand Down Expand Up @@ -130,13 +149,6 @@ export class Extractor {
return PackageJsonLookup.loadOwnPackageJson(__dirname);
}

private static _defaultLogger: ILogger = {
logVerbose: (message: string) => console.log('(Verbose) ' + message),
logInfo: (message: string) => console.log(message),
logWarning: (message: string) => console.warn(colors.yellow(message)),
logError: (message: string) => console.error(colors.red(message))
};

/**
* Load the api-extractor.json config file from the specified path, and then invoke API Extractor.
*/
Expand All @@ -155,18 +167,8 @@ export class Extractor {
options = { };
}

let mergedLogger: ILogger;
if (options && options.customLogger) {
mergedLogger = lodash.merge(lodash.clone(Extractor._defaultLogger), options.customLogger);
} else {
mergedLogger = Extractor._defaultLogger;
}
const monitoredLogger: MonitoredLogger = new MonitoredLogger(mergedLogger);

const localBuild: boolean = options.localBuild || false;

monitoredLogger.resetCounters();

let compilerState: CompilerState | undefined;
if (options.compilerState) {
compilerState = options.compilerState;
Expand All @@ -176,10 +178,13 @@ export class Extractor {

const collector: Collector = new Collector({
program: compilerState.program,
logger: monitoredLogger,
messageCallback: options.messageCallback,
extractorConfig: extractorConfig
});

const messageRouter: MessageRouter = collector.messageRouter;
messageRouter.showVerboseMessages = !!options.showVerboseMessages;

collector.analyze();

DocCommentEnhancer.analyze(collector);
Expand All @@ -189,7 +194,7 @@ export class Extractor {
const apiPackage: ApiPackage = modelBuilder.buildApiPackage();

if (extractorConfig.docModelEnabled) {
monitoredLogger.logVerbose('Writing: ' + extractorConfig.apiJsonFilePath);
messageRouter.logVerbose(ConsoleMessageId.WritingDocModelFile, 'Writing: ' + extractorConfig.apiJsonFilePath);
apiPackage.saveToJsonFile(extractorConfig.apiJsonFilePath, {
toolPackage: Extractor.packageName,
toolVersion: Extractor.version,
Expand All @@ -200,6 +205,8 @@ export class Extractor {
});
}

let apiReportChanged: boolean = false;

if (extractorConfig.apiReportEnabled) {
const actualApiReportPath: string = extractorConfig.reportTempFilePath;
const actualApiReviewShortPath: string = extractorConfig._getShortFilePath(extractorConfig.reportTempFilePath);
Expand All @@ -222,81 +229,77 @@ export class Extractor {
if (!ReviewFileGenerator.areEquivalentApiFileContents(actualApiReviewContent, expectedApiReviewContent)) {
if (!localBuild) {
// For production, issue a warning that will break the CI build.
monitoredLogger.logWarning('You have changed the public API signature for this project.'
messageRouter.logWarning(ConsoleMessageId.ApiReportNotCopied,
'You have changed the public API signature for this project.'
// @microsoft/gulp-core-build seems to run JSON.stringify() on the error messages for some reason,
// so try to avoid escaped characters:
+ ` Please overwrite ${expectedApiReviewShortPath} with a`
+ ` copy of ${actualApiReviewShortPath}`
+ ' and then request an API review. See the Git repository README.md for more info.');
} else {
// For a local build, just copy the file automatically.
monitoredLogger.logWarning('You have changed the public API signature for this project.'
messageRouter.logWarning(ConsoleMessageId.ApiReportCopied,
'You have changed the public API signature for this project.'
+ ` Updating ${expectedApiReviewShortPath}`);

FileSystem.writeFile(expectedApiReviewPath, actualApiReviewContent, {
ensureFolderExists: true,
convertLineEndings: NewlineKind.CrLf
});
}

apiReportChanged = true;
} else {
monitoredLogger.logVerbose(`The API signature is up to date: ${actualApiReviewShortPath}`);
messageRouter.logVerbose(ConsoleMessageId.ApiReportUnchanged,
`The API signature is up to date: ${actualApiReviewShortPath}`);
}
} else {
// NOTE: This warning seems like a nuisance, but it has caught genuine mistakes.
// For example, when projects were moved into category folders, the relative path for
// the API review files ended up in the wrong place.
monitoredLogger.logError(`The API review file has not been set up.`
messageRouter.logError(ConsoleMessageId.ApiReportMissing, `The API review file has not been set up.`
+ ` Do this by copying ${actualApiReviewShortPath}`
+ ` to ${expectedApiReviewShortPath} and committing it.`);
}
}

if (extractorConfig.rollupEnabled) {
Extractor._generateRollupDtsFile(collector, monitoredLogger,
extractorConfig.publicTrimmedFilePath,
DtsRollupKind.PublicRelease);

Extractor._generateRollupDtsFile(collector, monitoredLogger,
extractorConfig.betaTrimmedFilePath,
DtsRollupKind.BetaRelease);

Extractor._generateRollupDtsFile(collector, monitoredLogger,
extractorConfig.untrimmedFilePath,
DtsRollupKind.InternalRelease);
Extractor._generateRollupDtsFile(collector, extractorConfig.publicTrimmedFilePath, DtsRollupKind.PublicRelease);
Extractor._generateRollupDtsFile(collector, extractorConfig.betaTrimmedFilePath, DtsRollupKind.BetaRelease);
Extractor._generateRollupDtsFile(collector, extractorConfig.untrimmedFilePath, DtsRollupKind.InternalRelease);
}

if (extractorConfig.tsdocMetadataEnabled) {
// Write the tsdoc-metadata.json file for this project
PackageMetadataManager.writeTsdocMetadataFile(extractorConfig.tsdocMetadataFilePath);
}

// Show out all the messages that we collected during analysis
collector.messageRouter.reportMessagesToLogger(monitoredLogger, collector.workingPackage.packageFolder);
// Show all the messages that we collected during analysis
messageRouter.handleRemainingNonConsoleMessages();

// Determine success
let succeeded: boolean;
if (localBuild) {
// For a local build, fail if there were errors (but ignore warnings)
succeeded = monitoredLogger.errorCount === 0;
succeeded = messageRouter.errorCount === 0;
} else {
// For a production build, fail if there were any errors or warnings
succeeded = monitoredLogger.errorCount + monitoredLogger.warningCount === 0;
succeeded = messageRouter.errorCount + messageRouter.warningCount === 0;
}

return new ExtractorResult({
compilerState,
extractorConfig,
succeeded,
errorCount: monitoredLogger.errorCount,
warningCount: monitoredLogger.warningCount
apiReportChanged,
errorCount: messageRouter.errorCount,
warningCount: messageRouter.warningCount
});
}

private static _generateRollupDtsFile(collector: Collector, monitoredLogger: MonitoredLogger,
outputPath: string, dtsKind: DtsRollupKind): void {

private static _generateRollupDtsFile(collector: Collector, outputPath: string, dtsKind: DtsRollupKind): void {
if (outputPath !== '') {
monitoredLogger.logVerbose(`Writing package typings: ${outputPath}`);
collector.messageRouter.logVerbose(ConsoleMessageId.WritingDtsRollup, `Writing package typings: ${outputPath}`);
DtsRollupGenerator.writeTypingsFile(collector, outputPath, dtsKind);
}
}
Expand Down
Loading

0 comments on commit 4c07317

Please sign in to comment.