Skip to content

Commit b31c2e7

Browse files
authored
Merge pull request microsoft#960 from Microsoft/pgonzal/tsdoc-metadata
[api-extractor] Replace the old "tsdocFlavor" field with a dist/api-metadata.json output
2 parents 8d0fcab + 0fe4bf3 commit b31c2e7

File tree

20 files changed

+225
-94
lines changed

20 files changed

+225
-94
lines changed

apps/api-documenter/src/start.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@
33

44
import * as os from 'os';
55
import * as colors from 'colors';
6-
import * as path from 'path';
76

8-
import { FileConstants } from '@microsoft/node-core-library';
7+
import { PackageJsonLookup } from '@microsoft/node-core-library';
98

109
import { ApiDocumenterCommandLine } from './cli/ApiDocumenterCommandLine';
1110

12-
const myPackageJsonFilename: string = path.resolve(path.join(
13-
__dirname, '..', FileConstants.PackageJson)
14-
);
15-
const myPackageJson: { version: string } = require(myPackageJsonFilename);
11+
const myPackageVersion: string = PackageJsonLookup.loadOwnPackageJson(__dirname).version;
1612

17-
console.log(os.EOL + colors.bold(`api-documenter ${myPackageJson.version} `
13+
console.log(os.EOL + colors.bold(`api-documenter ${myPackageVersion} `
1814
+ colors.cyan(' - http://aka.ms/extractor') + os.EOL));
1915

2016
const parser: ApiDocumenterCommandLine = new ApiDocumenterCommandLine();

apps/api-extractor/.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"run",
1616
"-l"
1717
],
18-
"sourceMaps": false
18+
"sourceMaps": true
1919
},
2020
{
2121
"type": "node",

apps/api-extractor/src/ExtractorContext.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export interface IExtractorContextOptions {
5151
* abstract syntax tree.
5252
*/
5353
export class ExtractorContext {
54-
public typeChecker: ts.TypeChecker;
54+
public readonly program: ts.Program;
55+
public readonly typeChecker: ts.TypeChecker;
5556
public package: AstPackage;
5657

5758
/**
@@ -73,12 +74,12 @@ export class ExtractorContext {
7374

7475
public readonly validationRules: IExtractorValidationRulesConfig;
7576

77+
public readonly logger: ILogger;
78+
7679
// If the entry point is "C:\Folder\project\src\index.ts" and the nearest package.json
7780
// is "C:\Folder\project\package.json", then the packageFolder is "C:\Folder\project"
7881
private _packageFolder: string;
7982

80-
private _logger: ILogger;
81-
8283
constructor(options: IExtractorContextOptions) {
8384
this.packageJsonLookup = new PackageJsonLookup();
8485

@@ -97,7 +98,7 @@ export class ExtractorContext {
9798

9899
this.docItemLoader = new DocItemLoader(this._packageFolder);
99100

100-
this._logger = options.logger;
101+
this.logger = options.logger;
101102

102103
// This runs a full type analysis, and then augments the Abstract Syntax Tree (i.e. declarations)
103104
// with semantic information (i.e. symbols). The "diagnostics" are a subset of the everyday
@@ -107,6 +108,7 @@ export class ExtractorContext {
107108
this.reportError(`TypeScript: ${errorText}`, diagnostic.file, diagnostic.start);
108109
}
109110

111+
this.program = options.program;
110112
this.typeChecker = options.program.getTypeChecker();
111113

112114
const rootFile: ts.SourceFile | undefined = options.program.getSourceFile(options.entryPointFile);
@@ -146,10 +148,10 @@ export class ExtractorContext {
146148

147149
// Format the error so that VS Code can follow it. For example:
148150
// "src\MyClass.ts(15,1): The JSDoc tag "@blah" is not supported by AEDoc"
149-
this._logger.logError(`${shownPath}(${lineAndCharacter.line + 1},${lineAndCharacter.character + 1}): `
151+
this.logger.logError(`${shownPath}(${lineAndCharacter.line + 1},${lineAndCharacter.character + 1}): `
150152
+ message);
151153
} else {
152-
this._logger.logError(message);
154+
this.logger.logError(message);
153155
}
154156
}
155157

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
JsonFile,
1111
JsonSchema,
1212
Path,
13-
FileSystem
13+
FileSystem,
14+
PackageJsonLookup
1415
} from '@microsoft/node-core-library';
1516
import {
1617
IExtractorConfig,
@@ -25,6 +26,7 @@ import { ApiFileGenerator } from '../generators/ApiFileGenerator';
2526
import { DtsRollupGenerator, DtsRollupKind } from '../generators/dtsRollup/DtsRollupGenerator';
2627
import { MonitoredLogger } from './MonitoredLogger';
2728
import { TypeScriptMessageFormatter } from '../utils/TypeScriptMessageFormatter';
29+
import { PackageMetadataManager } from '../generators/dtsRollup/PackageMetadataManager';
2830

2931
/**
3032
* Options for {@link Extractor.processProject}.
@@ -119,6 +121,13 @@ export class Extractor {
119121
private readonly _monitoredLogger: MonitoredLogger;
120122
private readonly _absoluteRootFolder: string;
121123

124+
/**
125+
* The NPM package version for the currently executing instance of the "\@microsoft/api-extractor" library.
126+
*/
127+
public static get version(): string {
128+
return PackageJsonLookup.loadOwnPackageJson(__dirname).version;
129+
}
130+
122131
/**
123132
* Given a list of absolute file paths, return a list containing only the declaration
124133
* files. Duplicates are also eliminated.
@@ -399,6 +408,9 @@ export class Extractor {
399408

400409
this._generateRollupDtsFiles(context);
401410

411+
// Write the tsdoc-metadata.json file for this project
412+
PackageMetadataManager.writeTsdocMetadataFile(context.packageFolder);
413+
402414
if (this._localBuild) {
403415
// For a local build, fail if there were errors (but ignore warnings)
404416
return this._monitoredLogger.errorCount === 0;
@@ -486,7 +498,7 @@ export class Extractor {
486498
this._monitoredLogger.logVerbose(`Writing package typings: ${mainDtsRollupFullPath}`);
487499

488500
dtsRollupGenerator.writeTypingsFile(mainDtsRollupFullPath, dtsKind);
489-
}
501+
}
490502

491503
private _getShortFilePath(absolutePath: string): string {
492504
if (!path.isAbsolute(absolutePath)) {

apps/api-extractor/src/generators/dtsRollup/AstSymbolTable.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { AstSymbol } from './AstSymbol';
1313
import { AstImport } from './AstImport';
1414
import { AstEntryPoint, IExportedMember } from './AstEntryPoint';
1515
import { PackageMetadataManager } from './PackageMetadataManager';
16+
import { ILogger } from '../../extractor/ILogger';
1617

1718
/**
1819
* AstSymbolTable is the workhorse that builds AstSymbol and AstDeclaration objects.
@@ -22,6 +23,7 @@ import { PackageMetadataManager } from './PackageMetadataManager';
2223
* is "exported" or not.)
2324
*/
2425
export class AstSymbolTable {
26+
private readonly _program: ts.Program;
2527
private readonly _typeChecker: ts.TypeChecker;
2628
private readonly _packageJsonLookup: PackageJsonLookup;
2729
private readonly _packageMetadataManager: PackageMetadataManager;
@@ -54,10 +56,13 @@ export class AstSymbolTable {
5456
private readonly _astEntryPointsBySourceFile: Map<ts.SourceFile, AstEntryPoint>
5557
= new Map<ts.SourceFile, AstEntryPoint>();
5658

57-
public constructor(typeChecker: ts.TypeChecker, packageJsonLookup: PackageJsonLookup) {
59+
public constructor(program: ts.Program, typeChecker: ts.TypeChecker, packageJsonLookup: PackageJsonLookup,
60+
logger: ILogger) {
61+
62+
this._program = program;
5863
this._typeChecker = typeChecker;
5964
this._packageJsonLookup = packageJsonLookup;
60-
this._packageMetadataManager = new PackageMetadataManager(packageJsonLookup);
65+
this._packageMetadataManager = new PackageMetadataManager(packageJsonLookup, logger);
6166
}
6267

6368
/**
@@ -73,19 +78,6 @@ export class AstSymbolTable {
7378
throw new Error('Unable to find a root declaration for ' + sourceFile.fileName);
7479
}
7580

76-
if (!this._packageMetadataManager.isAedocSupportedFor(sourceFile.fileName)) {
77-
const packageJsonPath: string | undefined = this._packageJsonLookup
78-
.tryGetPackageJsonFilePathFor(sourceFile.fileName);
79-
80-
if (packageJsonPath) {
81-
throw new Error(`Please add a field such as "tsdoc": { "tsdocFlavor": "AEDoc" } to this file:\n`
82-
+ packageJsonPath);
83-
} else {
84-
throw new Error(`The specified entry point does not appear to have an associated package.json file:\n`
85-
+ sourceFile.fileName);
86-
}
87-
}
88-
8981
const exportSymbols: ts.Symbol[] = this._typeChecker.getExportsOfModule(rootFileSymbol) || [];
9082

9183
const exportedMembers: IExportedMember[] = [];
@@ -314,9 +306,11 @@ export class AstSymbolTable {
314306

315307
// If the file is from a package that does not support AEDoc, then we process the
316308
// symbol itself, but we don't attempt to process any parent/children of it.
317-
if (!this._packageMetadataManager.isAedocSupportedFor(
318-
followedSymbol.declarations[0].getSourceFile().fileName)) {
319-
nominal = true;
309+
const followedSymbolSourceFile: ts.SourceFile = followedSymbol.declarations[0].getSourceFile();
310+
if (this._program.isSourceFileFromExternalLibrary(followedSymbolSourceFile)) {
311+
if (!this._packageMetadataManager.isAedocSupportedFor(followedSymbolSourceFile.fileName)) {
312+
nominal = true;
313+
}
320314
}
321315

322316
let parentAstSymbol: AstSymbol | undefined = undefined;

apps/api-extractor/src/generators/dtsRollup/DtsRollupGenerator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ export class DtsRollupGenerator {
7575
this._context = context;
7676
this._typeChecker = context.typeChecker;
7777
this._tsdocParser = new tsdoc.TSDocParser();
78-
this._astSymbolTable = new AstSymbolTable(this._context.typeChecker, this._context.packageJsonLookup);
78+
this._astSymbolTable = new AstSymbolTable(this._context.program, this._context.typeChecker,
79+
this._context.packageJsonLookup, context.logger);
7980
}
8081

8182
/**

apps/api-extractor/src/generators/dtsRollup/PackageMetadataManager.ts

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4+
import * as path from 'path';
5+
46
import {
57
PackageJsonLookup,
6-
IPackageJson
8+
IPackageJson,
9+
FileSystem,
10+
JsonFile,
11+
NewlineKind
712
} from '@microsoft/node-core-library';
13+
import { Extractor } from '../../extractor/Extractor';
14+
import { ILogger } from '../../extractor/ILogger';
815

916
/**
1017
* Represents analyzed information for a package.json file.
@@ -26,37 +33,64 @@ export class PackageMetadata {
2633
*/
2734
public readonly aedocSupported: boolean;
2835

29-
private readonly _packageJsonLookup: PackageJsonLookup;
30-
31-
public constructor(packageJsonPath: string, packageJsonLookup: PackageJsonLookup) {
32-
this._packageJsonLookup = packageJsonLookup;
36+
public constructor(packageJsonPath: string, packageJson: IPackageJson, aedocSupported: boolean) {
3337
this.packageJsonPath = packageJsonPath;
34-
35-
this.packageJson = this._packageJsonLookup.loadPackageJson(packageJsonPath);
36-
37-
this.aedocSupported = false;
38-
39-
if (this.packageJson.tsdoc) {
40-
if (this.packageJson.tsdoc.tsdocFlavor) {
41-
if (this.packageJson.tsdoc.tsdocFlavor.toUpperCase() === 'AEDOC') {
42-
this.aedocSupported = true;
43-
}
44-
}
45-
}
38+
this.packageJson = packageJson;
39+
this.aedocSupported = aedocSupported;
4640
}
4741
}
4842

4943
/**
5044
* This class maintains a cache of analyzed information obtained from package.json
5145
* files. It is built on top of the PackageJsonLookup class.
46+
*
47+
* @remarks
48+
*
49+
* IMPORTANT: Don't use PackageMetadataManager to analyze source files from the current project:
50+
* 1. Files such as tsdoc-metadata.json may not have been built yet, and thus may contain incorrect information.
51+
* 2. The current project is not guaranteed to have a package.json file at all. For example, API Extractor can
52+
* be invoked on a bare .d.ts file.
53+
*
54+
* Use ts.program.isSourceFileFromExternalLibrary() to test source files before passing the to PackageMetadataManager.
5255
*/
5356
export class PackageMetadataManager {
57+
public static tsdocMetadataFilename: string = 'tsdoc-metadata.json';
58+
5459
private readonly _packageJsonLookup: PackageJsonLookup;
60+
private readonly _logger: ILogger;
5561
private readonly _packageMetadataByPackageJsonPath: Map<string, PackageMetadata>
5662
= new Map<string, PackageMetadata>();
5763

58-
public constructor(packageJsonLookup: PackageJsonLookup) {
64+
public static writeTsdocMetadataFile(packageJsonFolder: string): void {
65+
// This feature is still being standardized: https://github.com/Microsoft/tsdoc/issues/7
66+
// In the future we will use the @microsoft/tsdoc library to read this file.
67+
const tsdocMetadataPath: string = path.join(packageJsonFolder,
68+
'dist', PackageMetadataManager.tsdocMetadataFilename);
69+
70+
const fileObject: Object = {
71+
tsdocVersion: '0.12',
72+
toolPackages: [
73+
{
74+
packageName: '@microsoft/api-extractor',
75+
packageVersion: Extractor.version
76+
}
77+
]
78+
};
79+
80+
const fileContent: string =
81+
'// This file is read by tools that parse documentation comments conforming to the TSDoc standard.\n'
82+
+ '// It should be published with your NPM package. It should not be tracked by Git.\n'
83+
+ JsonFile.stringify(fileObject);
84+
85+
FileSystem.writeFile(tsdocMetadataPath, fileContent, {
86+
convertLineEndings: NewlineKind.CrLf,
87+
ensureFolderExists: true
88+
});
89+
}
90+
91+
public constructor(packageJsonLookup: PackageJsonLookup, logger: ILogger) {
5992
this._packageJsonLookup = packageJsonLookup;
93+
this._logger = logger;
6094
}
6195

6296
/**
@@ -72,16 +106,34 @@ export class PackageMetadataManager {
72106
}
73107
let packageMetadata: PackageMetadata | undefined
74108
= this._packageMetadataByPackageJsonPath.get(packageJsonFilePath);
109+
75110
if (!packageMetadata) {
76-
packageMetadata = new PackageMetadata(packageJsonFilePath, this._packageJsonLookup);
111+
const packageJson: IPackageJson = this._packageJsonLookup.loadPackageJson(packageJsonFilePath);
112+
113+
const packageJsonFolder: string = path.dirname(packageJsonFilePath);
114+
115+
// This feature is still being standardized: https://github.com/Microsoft/tsdoc/issues/7
116+
// In the future we will use the @microsoft/tsdoc library to read this file.
117+
let aedocSupported: boolean = false;
118+
119+
const tsdocMetadataPath: string = path.join(packageJsonFolder,
120+
'dist', PackageMetadataManager.tsdocMetadataFilename);
121+
122+
if (FileSystem.exists(tsdocMetadataPath)) {
123+
this._logger.logVerbose('Found metadata in ' + tsdocMetadataPath);
124+
// If the file exists at all, assume it was written by API Extractor
125+
aedocSupported = true;
126+
}
127+
128+
packageMetadata = new PackageMetadata(packageJsonFilePath, packageJson, aedocSupported);
77129
this._packageMetadataByPackageJsonPath.set(packageJsonFilePath, packageMetadata);
78130
}
131+
79132
return packageMetadata;
80133
}
81134

82135
/**
83-
* Returns true if the source file has an associated PackageMetadata object
84-
* with aedocSupported=true.
136+
* Returns true if the source file is part of a package whose .d.ts files support AEDoc annotations.
85137
*/
86138
public isAedocSupportedFor(sourceFilePath: string): boolean {
87139
const packageMetadata: PackageMetadata | undefined = this.tryFetchPackageMetadata(sourceFilePath);

apps/api-extractor/src/start.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,13 @@
33

44
import * as os from 'os';
55
import * as colors from 'colors';
6-
import * as path from 'path';
7-
8-
import { FileConstants } from '@microsoft/node-core-library';
96

107
import { ApiExtractorCommandLine } from './cli/ApiExtractorCommandLine';
8+
import { Extractor } from './extractor/Extractor';
119

12-
const myPackageJsonFilename: string = path.resolve(path.join(
13-
__dirname, '..', FileConstants.PackageJson)
14-
);
15-
const myPackageJson: { version: string } = require(myPackageJsonFilename);
10+
const myPackageVersion: string = Extractor.version;
1611

17-
console.log(os.EOL + colors.bold(`api-extractor ${myPackageJson.version} `
12+
console.log(os.EOL + colors.bold(`api-extractor ${myPackageVersion} `
1813
+ colors.cyan(' - http://aka.ms/extractor') + os.EOL));
1914

2015
const parser: ApiExtractorCommandLine = new ApiExtractorCommandLine();

0 commit comments

Comments
 (0)