Skip to content

Commit 938dfb2

Browse files
authored
Merge pull request microsoft#1359 from microsoft/octogonz/ae-diagnostics
[api-extractor] Add a "--diagnostics" command-line option
2 parents 08ab7d5 + 72798d1 commit 938dfb2

9 files changed

Lines changed: 187 additions & 26 deletions

File tree

apps/api-extractor/src/api/ConsoleMessageId.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,10 @@ export const enum ConsoleMessageId {
5858
/**
5959
* "Unable to create the API report file. Please make sure the target folder exists: ___"
6060
*/
61-
ApiReportFolderMissing = 'console-api-report-folder-missing'
61+
ApiReportFolderMissing = 'console-api-report-folder-missing',
62+
63+
/**
64+
* Used for the information printed when the "--diagnostics" flag is enabled.
65+
*/
66+
Diagnostics = 'console-diagnostics'
6267
}

apps/api-extractor/src/api/Extractor.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ export interface IExtractorInvokeOptions {
5050
*/
5151
showVerboseMessages?: boolean;
5252

53+
/**
54+
* If true, API Extractor will print diagnostic information used for troubleshooting problems.
55+
* These messages will be included as {@link ExtractorLogLevel.Verbose} output.
56+
* Setting `showDiagnostics=true` forces `showVerboseMessages=true`.
57+
*/
58+
showDiagnostics?: boolean;
59+
5360
/**
5461
* By default API Extractor uses its own TypeScript compiler version to analyze your project.
5562
* This can often cause compiler errors due to incompatibilities between different TS versions.
@@ -178,15 +185,31 @@ export class Extractor {
178185
compilerState = CompilerState.create(extractorConfig, options);
179186
}
180187

188+
const messageRouter: MessageRouter = new MessageRouter({
189+
workingPackageFolder: extractorConfig.packageFolder,
190+
messageCallback: options.messageCallback,
191+
messagesConfig: extractorConfig.messages || { },
192+
showVerboseMessages: !!options.showVerboseMessages,
193+
showDiagnostics: !!options.showDiagnostics
194+
});
195+
196+
if (messageRouter.showDiagnostics) {
197+
messageRouter.logDiagnosticHeader('Final prepared ExtractorConfig');
198+
messageRouter.logDiagnostic(extractorConfig.getDiagnosticDump());
199+
messageRouter.logDiagnosticFooter();
200+
201+
messageRouter.logDiagnosticHeader('Compiler options');
202+
const serializedOptions: object = MessageRouter.buildJsonDumpObject(compilerState.program.getCompilerOptions());
203+
messageRouter.logDiagnostic(JSON.stringify(serializedOptions, undefined, 2));
204+
messageRouter.logDiagnosticFooter();
205+
}
206+
181207
const collector: Collector = new Collector({
182208
program: compilerState.program,
183-
messageCallback: options.messageCallback,
209+
messageRouter,
184210
extractorConfig: extractorConfig
185211
});
186212

187-
const messageRouter: MessageRouter = collector.messageRouter;
188-
messageRouter.showVerboseMessages = !!options.showVerboseMessages;
189-
190213
collector.analyze();
191214

192215
DocCommentEnhancer.analyze(collector);

apps/api-extractor/src/api/ExtractorConfig.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
IExtractorMessagesConfig
2222
} from './IConfigFile';
2323
import { PackageMetadataManager } from '../analyzer/PackageMetadataManager';
24+
import { MessageRouter } from '../collector/MessageRouter';
2425

2526
/**
2627
* Tokens used during variable expansion of path fields from api-extractor.json.
@@ -220,6 +221,19 @@ export class ExtractorConfig {
220221
this.testMode = parameters.testMode;
221222
}
222223

224+
/**
225+
* Returns a JSON-like string representing the `ExtractorConfig` state, which can be printed to a console
226+
* for diagnostic purposes.
227+
*
228+
* @remarks
229+
* This is used by the "--diagnostics" command-line option. The string is not intended to be deserialized;
230+
* its format may be changed at any time.
231+
*/
232+
public getDiagnosticDump(): string {
233+
const result: object = MessageRouter.buildJsonDumpObject(this);
234+
return JSON.stringify(result, undefined, 2);
235+
}
236+
223237
/**
224238
* Returns a simplified file path for use in error messages.
225239
* @internal

apps/api-extractor/src/api/ExtractorMessage.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,16 +207,18 @@ export class ExtractorMessage {
207207
* src/folder/File.ts:123:4 - (ae-extra-release-tag) The doc comment should not contain more than one release tag.
208208
* ```
209209
*/
210-
public formatMessageWithLocation(workingPackageFolderPath: string): string {
210+
public formatMessageWithLocation(workingPackageFolderPath: string | undefined): string {
211211
let result: string = '';
212212

213213
if (this.sourceFilePath) {
214214
// Make the path relative to the workingPackageFolderPath
215215
let scrubbedPath: string = this.sourceFilePath;
216216

217-
// If it's under the working folder, make it a relative path
218-
if (Path.isUnderOrEqual(this.sourceFilePath, workingPackageFolderPath)) {
219-
scrubbedPath = path.relative(workingPackageFolderPath, this.sourceFilePath);
217+
if (workingPackageFolderPath !== undefined) {
218+
// If it's under the working folder, make it a relative path
219+
if (Path.isUnderOrEqual(this.sourceFilePath, workingPackageFolderPath)) {
220+
scrubbedPath = path.relative(workingPackageFolderPath, this.sourceFilePath);
221+
}
220222
}
221223

222224
// Convert it to a Unix-style path

apps/api-extractor/src/cli/RunAction.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class RunAction extends CommandLineAction {
2626
private _configFileParameter: CommandLineStringParameter;
2727
private _localParameter: CommandLineFlagParameter;
2828
private _verboseParameter: CommandLineFlagParameter;
29+
private _diagnosticsParameter: CommandLineFlagParameter;
2930
private _typescriptCompilerFolder: CommandLineStringParameter;
3031

3132
constructor(parser: ApiExtractorCommandLine) {
@@ -56,7 +57,13 @@ export class RunAction extends CommandLineAction {
5657
this._verboseParameter = this.defineFlagParameter({
5758
parameterLongName: '--verbose',
5859
parameterShortName: '-v',
59-
description: 'Show additional diagnostic messages in the output.'
60+
description: 'Show additional informational messages in the output.'
61+
});
62+
63+
this._diagnosticsParameter = this.defineFlagParameter({
64+
parameterLongName: '--diagnostics',
65+
description: 'Show diagnostic messages used for troubleshooting problems with API Extractor.'
66+
+ ' This flag also enables the "--verbose" flag.'
6067
});
6168

6269
this._typescriptCompilerFolder = this.defineStringParameter({
@@ -142,6 +149,7 @@ export class RunAction extends CommandLineAction {
142149
{
143150
localBuild: this._localParameter.value,
144151
showVerboseMessages: this._verboseParameter.value,
152+
showDiagnostics: this._diagnosticsParameter.value,
145153
typescriptCompilerFolder: typescriptCompilerFolder
146154
}
147155
);

apps/api-extractor/src/collector/Collector.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import { TypeScriptInternals } from '../analyzer/TypeScriptInternals';
2929
import { MessageRouter } from './MessageRouter';
3030
import { AstReferenceResolver } from '../analyzer/AstReferenceResolver';
3131
import { ExtractorConfig } from '../api/ExtractorConfig';
32-
import { ExtractorMessage } from '../api/ExtractorMessage';
3332

3433
/**
3534
* Options for Collector constructor.
@@ -45,7 +44,7 @@ export interface ICollectorOptions {
4544
*/
4645
program: ts.Program;
4746

48-
messageCallback: ((message: ExtractorMessage) => void) | undefined;
47+
messageRouter: MessageRouter;
4948

5049
extractorConfig: ExtractorConfig;
5150
}
@@ -108,10 +107,7 @@ export class Collector {
108107
entryPointSourceFile
109108
});
110109

111-
this.messageRouter = new MessageRouter(
112-
this.workingPackage.packageFolder,
113-
options.messageCallback,
114-
options.extractorConfig.messages || { });
110+
this.messageRouter = options.messageRouter;
115111

116112
this.program = options.program;
117113
this.typeChecker = options.program.getTypeChecker();
@@ -171,6 +167,20 @@ export class Collector {
171167
this.messageRouter.addCompilerDiagnostic(diagnostic);
172168
}
173169

170+
if (this.messageRouter.showDiagnostics) {
171+
this.messageRouter.logDiagnosticHeader('Root filenames');
172+
for (const fileName of this.program.getRootFileNames()) {
173+
this.messageRouter.logDiagnostic(fileName);
174+
}
175+
this.messageRouter.logDiagnosticFooter();
176+
177+
this.messageRouter.logDiagnosticHeader('Files analyzed by compiler');
178+
for (const sourceFile of this.program.getSourceFiles()) {
179+
this.messageRouter.logDiagnostic(sourceFile.fileName);
180+
}
181+
this.messageRouter.logDiagnosticFooter();
182+
}
183+
174184
// Build the entry point
175185
const entryPointSourceFile: ts.SourceFile = this.workingPackage.entryPointSourceFile;
176186

apps/api-extractor/src/collector/MessageRouter.ts

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,18 @@ interface IReportingRule {
3030
addToApiReportFile: boolean;
3131
}
3232

33+
export interface IMessageRouterOptions {
34+
workingPackageFolder: string | undefined;
35+
messageCallback: ((message: ExtractorMessage) => void) | undefined;
36+
messagesConfig: IExtractorMessagesConfig;
37+
showVerboseMessages: boolean;
38+
showDiagnostics: boolean;
39+
}
40+
3341
export class MessageRouter {
34-
private readonly _workingPackageFolder: string;
42+
public static readonly DIAGNOSTICS_LINE: string = '============================================================';
43+
44+
private readonly _workingPackageFolder: string | undefined;
3545
private readonly _messageCallback: ((message: ExtractorMessage) => void) | undefined;
3646

3747
// All messages
@@ -53,20 +63,30 @@ export class MessageRouter {
5363

5464
public errorCount: number = 0;
5565
public warningCount: number = 0;
56-
public showVerboseMessages: boolean = false;
5766

58-
public constructor(workingPackageFolder: string,
59-
messageCallback: ((message: ExtractorMessage) => void) | undefined,
60-
messagesConfig: IExtractorMessagesConfig) {
67+
/**
68+
* See {@link IExtractorInvokeOptions.showVerboseMessages}
69+
*/
70+
public readonly showVerboseMessages: boolean;
71+
72+
/**
73+
* See {@link IExtractorInvokeOptions.showDiagnostics}
74+
*/
75+
public readonly showDiagnostics: boolean;
6176

62-
this._workingPackageFolder = workingPackageFolder;
63-
this._messageCallback = messageCallback;
77+
public constructor(options: IMessageRouterOptions) {
78+
this._workingPackageFolder = options.workingPackageFolder;
79+
this._messageCallback = options.messageCallback;
6480

6581
this._messages = [];
6682
this._associatedMessagesForAstDeclaration = new Map<AstDeclaration, ExtractorMessage[]>();
6783
this._sourceMapper = new SourceMapper();
6884

69-
this._applyMessagesConfig(messagesConfig);
85+
// showDiagnostics implies showVerboseMessages
86+
this.showVerboseMessages = options.showVerboseMessages || options.showDiagnostics;
87+
this.showDiagnostics = options.showDiagnostics;
88+
89+
this._applyMessagesConfig(options.messagesConfig);
7090
}
7191

7292
/**
@@ -222,6 +242,57 @@ export class MessageRouter {
222242
}
223243
}
224244

245+
/**
246+
* Recursively collects the primitive members (numbers, strings, arrays, etc) into an object that
247+
* is JSON serializable. This is used by the "--diagnostics" feature to dump the state of configuration objects.
248+
*
249+
* @returns a JSON serializable object (possibly including `null` values)
250+
* or `undefined` if the input cannot be represented as JSON
251+
*/
252+
// tslint:disable-next-line:no-any
253+
public static buildJsonDumpObject(input: any): any | undefined {
254+
if (input === null || input === undefined) {
255+
// tslint:disable-next-line:no-null-keyword
256+
return null; // JSON uses null instead of undefined
257+
}
258+
259+
switch (typeof input) {
260+
case 'boolean':
261+
case 'number':
262+
case 'string':
263+
return input;
264+
case 'object':
265+
if (Array.isArray(input)) {
266+
// tslint:disable-next-line:no-any
267+
const outputArray: any[] = [];
268+
for (const element of input) {
269+
// tslint:disable-next-line:no-any
270+
const serializedElement: any = MessageRouter.buildJsonDumpObject(element);
271+
if (serializedElement !== undefined) {
272+
outputArray.push(serializedElement);
273+
}
274+
}
275+
return outputArray;
276+
}
277+
278+
const outputObject: object = { };
279+
for (const key of Object.getOwnPropertyNames(input)) {
280+
// tslint:disable-next-line:no-any
281+
const value: any = input[key];
282+
283+
// tslint:disable-next-line:no-any
284+
const serializedValue: any = MessageRouter.buildJsonDumpObject(value);
285+
286+
if (serializedValue !== undefined) {
287+
outputObject[key] = serializedValue;
288+
}
289+
}
290+
return outputObject;
291+
}
292+
293+
return undefined;
294+
}
295+
225296
/**
226297
* Record this message in _associatedMessagesForAstDeclaration
227298
*/
@@ -384,6 +455,20 @@ export class MessageRouter {
384455
}));
385456
}
386457

458+
public logDiagnosticHeader(title: string): void {
459+
this.logDiagnostic(MessageRouter.DIAGNOSTICS_LINE);
460+
this.logDiagnostic(`DIAGNOSTIC: ` + title);
461+
this.logDiagnostic(MessageRouter.DIAGNOSTICS_LINE);
462+
}
463+
464+
public logDiagnosticFooter(): void {
465+
this.logDiagnostic(MessageRouter.DIAGNOSTICS_LINE + '\n');
466+
}
467+
468+
public logDiagnostic(message: string): void {
469+
this.logVerbose(ConsoleMessageId.Diagnostics, message);
470+
}
471+
387472
/**
388473
* Give the calling application a chance to handle the `ExtractorMessage`, and if not, display it on the console.
389474
*/
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/api-extractor",
5+
"comment": "Add a \"--diagnostics\" command-line option to help when troubleshooting problems",
6+
"type": "minor"
7+
}
8+
],
9+
"packageName": "@microsoft/api-extractor",
10+
"email": "4673363+octogonz@users.noreply.github.com"
11+
}

common/reviews/api/api-extractor.api.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as tsdoc from '@microsoft/tsdoc';
1313
export class CompilerState {
1414
static create(extractorConfig: ExtractorConfig, options?: ICompilerStateCreateOptions): CompilerState;
1515
readonly program: ts.Program;
16-
}
16+
}
1717

1818
// @public
1919
export const enum ConsoleMessageId {
@@ -22,6 +22,7 @@ export const enum ConsoleMessageId {
2222
ApiReportFolderMissing = "console-api-report-folder-missing",
2323
ApiReportNotCopied = "console-api-report-not-copied",
2424
ApiReportUnchanged = "console-api-report-unchanged",
25+
Diagnostics = "console-diagnostics",
2526
FoundTSDocMetadata = "console-found-tsdoc-metadata",
2627
WritingDocModelFile = "console-writing-doc-model-file",
2728
WritingDtsRollup = "console-writing-dts-rollup"
@@ -42,6 +43,7 @@ export class ExtractorConfig {
4243
readonly betaTrimmedFilePath: string;
4344
readonly docModelEnabled: boolean;
4445
static readonly FILENAME: string;
46+
getDiagnosticDump(): string;
4547
// @internal
4648
_getShortFilePath(absolutePath: string): string;
4749
static hasDtsFileExtension(filePath: string): boolean;
@@ -84,7 +86,7 @@ export class ExtractorMessage {
8486
// @internal
8587
constructor(options: IExtractorMessageOptions);
8688
readonly category: ExtractorMessageCategory;
87-
formatMessageWithLocation(workingPackageFolderPath: string): string;
89+
formatMessageWithLocation(workingPackageFolderPath: string | undefined): string;
8890
// (undocumented)
8991
formatMessageWithoutLocation(): string;
9092
handled: boolean;
@@ -216,6 +218,7 @@ export interface IExtractorInvokeOptions {
216218
compilerState?: CompilerState;
217219
localBuild?: boolean;
218220
messageCallback?: (message: ExtractorMessage) => void;
221+
showDiagnostics?: boolean;
219222
showVerboseMessages?: boolean;
220223
typescriptCompilerFolder?: string;
221224
}

0 commit comments

Comments
 (0)