-
Notifications
You must be signed in to change notification settings - Fork 607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[api-extractor] Replace the old "tsdocFlavor" field with a dist/tsdoc-metadata.json output #960
Changes from 7 commits
924dc88
58a4300
6700652
1efb25c
51fd48b
181a8e8
c28c261
e97a5f0
47db611
9c32be6
0fe4bf3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,8 @@ import { | |
JsonFile, | ||
JsonSchema, | ||
Path, | ||
FileSystem | ||
FileSystem, | ||
PackageJsonLookup | ||
} from '@microsoft/node-core-library'; | ||
import { | ||
IExtractorConfig, | ||
|
@@ -25,6 +26,7 @@ import { ApiFileGenerator } from '../generators/ApiFileGenerator'; | |
import { DtsRollupGenerator, DtsRollupKind } from '../generators/dtsRollup/DtsRollupGenerator'; | ||
import { MonitoredLogger } from './MonitoredLogger'; | ||
import { TypeScriptMessageFormatter } from '../utils/TypeScriptMessageFormatter'; | ||
import { PackageMetadataManager } from '../generators/dtsRollup/PackageMetadataManager'; | ||
|
||
/** | ||
* Options for {@link Extractor.processProject}. | ||
|
@@ -119,6 +121,13 @@ export class Extractor { | |
private readonly _monitoredLogger: MonitoredLogger; | ||
private readonly _absoluteRootFolder: string; | ||
|
||
/** | ||
* The NPM package version for the currently executing instance of the "\@microsoft/api-extractor" library. | ||
*/ | ||
public static get version(): string { | ||
return PackageJsonLookup.loadOwnPackageJson(__dirname, '../..').version; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. #ByDesign |
||
} | ||
|
||
/** | ||
* Given a list of absolute file paths, return a list containing only the declaration | ||
* files. Duplicates are also eliminated. | ||
|
@@ -399,6 +408,9 @@ export class Extractor { | |
|
||
this._generateRollupDtsFiles(context); | ||
|
||
// Write the tsdoc-metadata.json file for this project | ||
PackageMetadataManager.writeTsdocMetadataFile(context.packageFolder); | ||
|
||
if (this._localBuild) { | ||
// For a local build, fail if there were errors (but ignore warnings) | ||
return this._monitoredLogger.errorCount === 0; | ||
|
@@ -486,7 +498,7 @@ export class Extractor { | |
this._monitoredLogger.logVerbose(`Writing package typings: ${mainDtsRollupFullPath}`); | ||
|
||
dtsRollupGenerator.writeTypingsFile(mainDtsRollupFullPath, dtsKind); | ||
} | ||
} | ||
|
||
private _getShortFilePath(absolutePath: string): string { | ||
if (!path.isAbsolute(absolutePath)) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ import { AstSymbol } from './AstSymbol'; | |
import { AstImport } from './AstImport'; | ||
import { AstEntryPoint, IExportedMember } from './AstEntryPoint'; | ||
import { PackageMetadataManager } from './PackageMetadataManager'; | ||
import { ILogger } from '../../extractor/ILogger'; | ||
|
||
/** | ||
* AstSymbolTable is the workhorse that builds AstSymbol and AstDeclaration objects. | ||
|
@@ -22,6 +23,7 @@ import { PackageMetadataManager } from './PackageMetadataManager'; | |
* is "exported" or not.) | ||
*/ | ||
export class AstSymbolTable { | ||
private readonly _program: ts.Program; | ||
private readonly _typeChecker: ts.TypeChecker; | ||
private readonly _packageJsonLookup: PackageJsonLookup; | ||
private readonly _packageMetadataManager: PackageMetadataManager; | ||
|
@@ -54,10 +56,13 @@ export class AstSymbolTable { | |
private readonly _astEntryPointsBySourceFile: Map<ts.SourceFile, AstEntryPoint> | ||
= new Map<ts.SourceFile, AstEntryPoint>(); | ||
|
||
public constructor(typeChecker: ts.TypeChecker, packageJsonLookup: PackageJsonLookup) { | ||
public constructor(program: ts.Program, typeChecker: ts.TypeChecker, packageJsonLookup: PackageJsonLookup, | ||
logger: ILogger) { | ||
|
||
this._program = program; | ||
this._typeChecker = typeChecker; | ||
this._packageJsonLookup = packageJsonLookup; | ||
this._packageMetadataManager = new PackageMetadataManager(packageJsonLookup); | ||
this._packageMetadataManager = new PackageMetadataManager(packageJsonLookup, logger); | ||
} | ||
|
||
/** | ||
|
@@ -73,16 +78,18 @@ export class AstSymbolTable { | |
throw new Error('Unable to find a root declaration for ' + sourceFile.fileName); | ||
} | ||
|
||
if (!this._packageMetadataManager.isAedocSupportedFor(sourceFile.fileName)) { | ||
const packageJsonPath: string | undefined = this._packageJsonLookup | ||
.tryGetPackageJsonFilePathFor(sourceFile.fileName); | ||
|
||
if (packageJsonPath) { | ||
throw new Error(`Please add a field such as "tsdoc": { "tsdocFlavor": "AEDoc" } to this file:\n` | ||
+ packageJsonPath); | ||
} else { | ||
throw new Error(`The specified entry point does not appear to have an associated package.json file:\n` | ||
+ sourceFile.fileName); | ||
if (this._program.isSourceFileFromExternalLibrary(sourceFile)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this right, or is this condition backwards? #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's right. If the source file is NOT from an external library, then it's part of the project we're analyzing, which obviously supports API Extractor. In reply to: 236575683 [](ancestors = 236575683) |
||
if (!this._packageMetadataManager.isAedocSupportedFor(sourceFile.fileName)) { | ||
const packageJsonPath: string | undefined = this._packageJsonLookup | ||
.tryGetPackageJsonFilePathFor(sourceFile.fileName); | ||
|
||
if (packageJsonPath) { | ||
throw new Error(`Please add a field such as "tsdoc": { "tsdocFlavor": "AEDoc" } to this file:\n` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't we say we wanted to just use the metadata file if we're generating a rollup? #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
+ packageJsonPath); | ||
} else { | ||
throw new Error(`The specified entry point does not appear to have an associated package.json file:\n` | ||
+ sourceFile.fileName); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -314,9 +321,11 @@ export class AstSymbolTable { | |
|
||
// If the file is from a package that does not support AEDoc, then we process the | ||
// symbol itself, but we don't attempt to process any parent/children of it. | ||
if (!this._packageMetadataManager.isAedocSupportedFor( | ||
followedSymbol.declarations[0].getSourceFile().fileName)) { | ||
nominal = true; | ||
const followedSymbolSourceFile: ts.SourceFile = followedSymbol.declarations[0].getSourceFile(); | ||
if (this._program.isSourceFileFromExternalLibrary(followedSymbolSourceFile)) { | ||
if (!this._packageMetadataManager.isAedocSupportedFor(followedSymbolSourceFile.fileName)) { | ||
nominal = true; | ||
} | ||
} | ||
|
||
let parentAstSymbol: AstSymbol | undefined = undefined; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,17 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | ||
// See LICENSE in the project root for license information. | ||
|
||
import * as path from 'path'; | ||
|
||
import { | ||
PackageJsonLookup, | ||
IPackageJson | ||
IPackageJson, | ||
FileSystem, | ||
JsonFile, | ||
NewlineKind | ||
} from '@microsoft/node-core-library'; | ||
import { Extractor } from '../../extractor/Extractor'; | ||
import { ILogger } from '../../extractor/ILogger'; | ||
|
||
/** | ||
* Represents analyzed information for a package.json file. | ||
|
@@ -26,37 +33,64 @@ export class PackageMetadata { | |
*/ | ||
public readonly aedocSupported: boolean; | ||
|
||
private readonly _packageJsonLookup: PackageJsonLookup; | ||
|
||
public constructor(packageJsonPath: string, packageJsonLookup: PackageJsonLookup) { | ||
this._packageJsonLookup = packageJsonLookup; | ||
public constructor(packageJsonPath: string, packageJson: IPackageJson, aedocSupported: boolean) { | ||
this.packageJsonPath = packageJsonPath; | ||
|
||
this.packageJson = this._packageJsonLookup.loadPackageJson(packageJsonPath); | ||
|
||
this.aedocSupported = false; | ||
|
||
if (this.packageJson.tsdoc) { | ||
if (this.packageJson.tsdoc.tsdocFlavor) { | ||
if (this.packageJson.tsdoc.tsdocFlavor.toUpperCase() === 'AEDOC') { | ||
this.aedocSupported = true; | ||
} | ||
} | ||
} | ||
this.packageJson = packageJson; | ||
this.aedocSupported = aedocSupported; | ||
} | ||
} | ||
|
||
/** | ||
* This class maintains a cache of analyzed information obtained from package.json | ||
* files. It is built on top of the PackageJsonLookup class. | ||
* | ||
* @remarks | ||
* | ||
* IMPORTANT: Don't use PackageMetadataManager to analyze source files from the current project: | ||
* 1. Files such as tsdoc-metadata.json may not have been built yet, and thus may contain incorrect information. | ||
* 2. The current project is not guaranteed to have a package.json file at all. For example, API Extractor can | ||
* be invoked on a bare .d.ts file. | ||
* | ||
* Use ts.program.isSourceFileFromExternalLibrary() to test source files before passing the to PackageMetadataManager. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would the perf impact be if we tested for this in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally if you're inside I could add an assertion here to catch that mistake however.
In reply to: 236576224 [](ancestors = 236576224) |
||
*/ | ||
export class PackageMetadataManager { | ||
public static tsdocMetadataFilename: string = 'tsdoc-metadata.json'; | ||
|
||
private readonly _packageJsonLookup: PackageJsonLookup; | ||
private readonly _logger: ILogger; | ||
private readonly _packageMetadataByPackageJsonPath: Map<string, PackageMetadata> | ||
= new Map<string, PackageMetadata>(); | ||
|
||
public constructor(packageJsonLookup: PackageJsonLookup) { | ||
public static writeTsdocMetadataFile(packageJsonFolder: string): void { | ||
// This feature is still being standardized: https://github.com/Microsoft/tsdoc/issues/7 | ||
// In the future we will use the @microsoft/tsdoc library to read this file. | ||
const tsdocMetadataPath: string = path.join(packageJsonFolder, | ||
'dist', PackageMetadataManager.tsdocMetadataFilename); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rollup file may not exist. But we could put it in the same directory as the default entry point for the NPM package. In reply to: 236576554 [](ancestors = 236576554) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
const fileObject: Object = { | ||
tsdocVersion: '0.12', | ||
toolPackages: [ | ||
{ | ||
packageName: '@microsoft/api-extractor', | ||
packageVersion: Extractor.version | ||
} | ||
] | ||
}; | ||
|
||
const fileContent: string = | ||
'// This file is read by tools that parse documentation comments conforming to the TSDoc standard.\n' | ||
+ '// It should be published with your NPM package. It should not be tracked by Git.\n' | ||
+ JsonFile.stringify(fileObject); | ||
|
||
FileSystem.writeFile(tsdocMetadataPath, fileContent, { | ||
convertLineEndings: NewlineKind.CrLf, | ||
ensureFolderExists: true | ||
}); | ||
} | ||
|
||
public constructor(packageJsonLookup: PackageJsonLookup, logger: ILogger) { | ||
this._packageJsonLookup = packageJsonLookup; | ||
this._logger = logger; | ||
} | ||
|
||
/** | ||
|
@@ -72,16 +106,34 @@ export class PackageMetadataManager { | |
} | ||
let packageMetadata: PackageMetadata | undefined | ||
= this._packageMetadataByPackageJsonPath.get(packageJsonFilePath); | ||
|
||
if (!packageMetadata) { | ||
packageMetadata = new PackageMetadata(packageJsonFilePath, this._packageJsonLookup); | ||
const packageJson: IPackageJson = this._packageJsonLookup.loadPackageJson(packageJsonFilePath); | ||
|
||
const packageJsonFolder: string = path.dirname(packageJsonFilePath); | ||
|
||
// This feature is still being standardized: https://github.com/Microsoft/tsdoc/issues/7 | ||
// In the future we will use the @microsoft/tsdoc library to read this file. | ||
let aedocSupported: boolean = false; | ||
|
||
const tsdocMetadataPath: string = path.join(packageJsonFolder, | ||
'dist', PackageMetadataManager.tsdocMetadataFilename); | ||
|
||
if (FileSystem.exists(tsdocMetadataPath)) { | ||
this._logger.logVerbose('Found metadata in ' + tsdocMetadataPath); | ||
// If the file exists at all, assume it was written by API Extractor | ||
aedocSupported = true; | ||
} | ||
|
||
packageMetadata = new PackageMetadata(packageJsonFilePath, packageJson, aedocSupported); | ||
this._packageMetadataByPackageJsonPath.set(packageJsonFilePath, packageMetadata); | ||
} | ||
|
||
return packageMetadata; | ||
} | ||
|
||
/** | ||
* Returns true if the source file has an associated PackageMetadata object | ||
* with aedocSupported=true. | ||
* Returns true if the source file is part of a package whose .d.ts files support AEDoc annotations. | ||
*/ | ||
public isAedocSupportedFor(sourceFilePath: string): boolean { | ||
const packageMetadata: PackageMetadata | undefined = this.tryFetchPackageMetadata(sourceFilePath); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,18 +3,14 @@ | |
|
||
import * as os from 'os'; | ||
import * as colors from 'colors'; | ||
import * as path from 'path'; | ||
|
||
import { FileConstants } from '@microsoft/node-core-library'; | ||
import { PackageJsonLookup } from '@microsoft/node-core-library'; | ||
|
||
import { ApiExtractorCommandLine } from './cli/ApiExtractorCommandLine'; | ||
|
||
const myPackageJsonFilename: string = path.resolve(path.join( | ||
__dirname, '..', FileConstants.PackageJson) | ||
); | ||
const myPackageJson: { version: string } = require(myPackageJsonFilename); | ||
const myPackageVersion: string = PackageJsonLookup.loadOwnPackageJson(__dirname, '..').version; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should just use |
||
|
||
console.log(os.EOL + colors.bold(`api-extractor ${myPackageJson.version} ` | ||
console.log(os.EOL + colors.bold(`api-extractor ${myPackageVersion} ` | ||
+ colors.cyan(' - http://aka.ms/extractor') + os.EOL)); | ||
|
||
const parser: ApiExtractorCommandLine = new ApiExtractorCommandLine(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you actually need the
..
? #ResolvedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, package.json is above the src folder.
We could walk upwards to probe for package.json, but that's inefficient and error-prone for a scenario that is supposed to be basically hardcoded.
In reply to: 236574942 [](ancestors = 236574942)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think you persuaded me
In reply to: 236587957 [](ancestors = 236587957,236574942)