Skip to content

Commit 3bcff3c

Browse files
committed
Adding an option to api-extractor to get libraries from another version of the compiler.
1 parent 6ef1cf9 commit 3bcff3c

3 files changed

Lines changed: 114 additions & 14 deletions

File tree

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import * as path from 'path';
77
import {
88
JsonFile,
99
PackageJsonLookup,
10-
FileSystem
10+
FileSystem,
11+
IPackageJson
1112
} from '@microsoft/node-core-library';
1213

1314
import {
@@ -27,6 +28,7 @@ export class RunAction extends CommandLineAction {
2728
private _parser: ApiExtractorCommandLine;
2829
private _configFileParameter: CommandLineStringParameter;
2930
private _localParameter: CommandLineFlagParameter;
31+
private _typescriptLibPackagePath: CommandLineStringParameter;
3032

3133
constructor(parser: ApiExtractorCommandLine) {
3234
super({
@@ -44,6 +46,7 @@ export class RunAction extends CommandLineAction {
4446
argumentName: 'FILE',
4547
description: `Use the specified ${AE_CONFIG_FILENAME} file path, rather than guessing its location`
4648
});
49+
4750
this._localParameter = this.defineFlagParameter({
4851
parameterLongName: '--local',
4952
parameterShortName: '-l',
@@ -52,11 +55,45 @@ export class RunAction extends CommandLineAction {
5255
+ ' normally be performed for a ship/production build. For example, the *.api.ts'
5356
+ ' review file is automatically copied in a local build.'
5457
});
58+
59+
this._typescriptLibPackagePath = this.defineStringParameter({
60+
parameterLongName: '--typescript-lib-package',
61+
argumentName: 'PATH',
62+
description: 'If specified, use typings specified in the project\'s compilerOptions -> lib option'
63+
+ ' from this TypeScript compiler package. This option is experimental.'
64+
});
5565
}
5666

5767
protected onExecute(): Promise<void> { // override
68+
const lookup: PackageJsonLookup = new PackageJsonLookup();
5869
let configFilename: string;
5970

71+
let typescriptLibPackagePath: string | undefined = this._typescriptLibPackagePath.value;
72+
if (typescriptLibPackagePath) {
73+
typescriptLibPackagePath = path.normalize(typescriptLibPackagePath);
74+
75+
if (FileSystem.exists(typescriptLibPackagePath)) {
76+
typescriptLibPackagePath = lookup.tryGetPackageFolderFor(typescriptLibPackagePath);
77+
const typescriptLibPackageJson: IPackageJson | undefined = typescriptLibPackagePath
78+
? lookup.tryLoadPackageJsonFor(typescriptLibPackagePath)
79+
: undefined;
80+
if (!typescriptLibPackageJson) {
81+
throw new Error(
82+
`The path specified in the ${this._typescriptLibPackagePath.longName} parameter is not a package.`
83+
);
84+
} else if (typescriptLibPackageJson.name !== 'typescript') {
85+
throw new Error(
86+
`The path specified in the ${this._typescriptLibPackagePath.longName} parameter is not a TypeScript`
87+
+ ' compiler package.'
88+
);
89+
}
90+
} else {
91+
throw new Error(
92+
`The path specified in the ${this._typescriptLibPackagePath.longName} parameter does not exist.`
93+
);
94+
}
95+
}
96+
6097
if (this._configFileParameter.value) {
6198
configFilename = path.normalize(this._configFileParameter.value);
6299
if (!FileSystem.exists(configFilename)) {
@@ -65,7 +102,6 @@ export class RunAction extends CommandLineAction {
65102
} else {
66103
// Otherwise, figure out which project we're in and look for the config file
67104
// at the project root
68-
const lookup: PackageJsonLookup = new PackageJsonLookup();
69105
const packageFolder: string | undefined = lookup.tryGetPackageFolderFor('.');
70106

71107
if (packageFolder) {
@@ -83,9 +119,13 @@ export class RunAction extends CommandLineAction {
83119
}
84120

85121
const config: IExtractorConfig = JsonFile.loadAndValidate(configFilename, Extractor.jsonSchema);
86-
const extractor: Extractor = new Extractor(config, {
87-
localBuild: this._localParameter.value
88-
});
122+
const extractor: Extractor = new Extractor(
123+
config,
124+
{
125+
localBuild: this._localParameter.value,
126+
typescriptLibPackagePath: typescriptLibPackagePath
127+
}
128+
);
89129

90130
if (!extractor.processProject()) {
91131
console.log(os.EOL + colors.yellow('API Extractor completed with errors or warnings'));

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

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ export interface IExtractorOptions {
6363
* The default value is false.
6464
*/
6565
localBuild?: boolean;
66+
67+
/**
68+
* If specified, use typings specified in the project's compilerOptions -> lib option
69+
* from this TypeScript compiler package.
70+
*
71+
* @alpha
72+
*/
73+
typescriptLibPackagePath?: string;
6674
}
6775

6876
/**
@@ -79,7 +87,7 @@ export class Extractor {
7987
private static _defaultConfig: Partial<IExtractorConfig> = JsonFile.load(path.join(__dirname,
8088
'./api-extractor-defaults.json'));
8189

82-
private static _outputFileExtensionRegExp: RegExp = /\.d\.ts$/i;
90+
private static _declarationFileExtensionRegExp: RegExp = /\.d\.ts$/i;
8391

8492
private static _defaultLogger: ILogger = {
8593
logVerbose: (message: string) => console.log('(Verbose) ' + message),
@@ -124,7 +132,7 @@ export class Extractor {
124132
throw new Error('Input file is not an absolute path: ' + inputFilePath);
125133
}
126134

127-
if (Extractor._outputFileExtensionRegExp.test(inputFilePath)) {
135+
if (Extractor._declarationFileExtensionRegExp.test(inputFilePath)) {
128136
analysisFilePaths.push(inputFilePath);
129137
}
130138
}
@@ -173,15 +181,22 @@ export class Extractor {
173181
tsconfig = JsonFile.load(path.join(this._absoluteRootFolder, 'tsconfig.json'));
174182
}
175183

176-
const commandLine: ts.ParsedCommandLine = ts.parseJsonConfigFileContent(tsconfig,
177-
ts.sys, this._absoluteRootFolder);
184+
const commandLine: ts.ParsedCommandLine = ts.parseJsonConfigFileContent(
185+
tsconfig,
186+
ts.sys,
187+
this._absoluteRootFolder
188+
);
189+
190+
this._updateCommandLineForTypescriptLibPackage(commandLine, options);
178191

179192
const normalizedEntryPointFile: string = path.normalize(
180-
path.resolve(this._absoluteRootFolder, this.actualConfig.project.entryPointSourceFile));
193+
path.resolve(this._absoluteRootFolder, this.actualConfig.project.entryPointSourceFile)
194+
);
181195

182-
// Append the normalizedEntryPointFile and remove any source files from the list
183-
const analysisFilePaths: string[] = Extractor.generateFilePathsForAnalysis(commandLine.fileNames
184-
.concat(normalizedEntryPointFile));
196+
// Append the normalizedEntryPointFile and remove any non-declaration files from the list
197+
const analysisFilePaths: string[] = Extractor.generateFilePathsForAnalysis(
198+
commandLine.fileNames.concat(normalizedEntryPointFile)
199+
);
185200

186201
this._program = ts.createProgram(analysisFilePaths, commandLine.options);
187202

@@ -262,7 +277,7 @@ export class Extractor {
262277
throw new Error('The configuration object wasn\'t normalized properly');
263278
}
264279

265-
if (!Extractor._outputFileExtensionRegExp.test(projectConfig.entryPointSourceFile)) {
280+
if (!Extractor._declarationFileExtensionRegExp.test(projectConfig.entryPointSourceFile)) {
266281
throw new Error('The entry point is not a declaration file: ' + projectConfig.entryPointSourceFile);
267282
}
268283

@@ -442,4 +457,47 @@ export class Extractor {
442457
}
443458
return path.relative(this._absoluteRootFolder, absolutePath).replace(/\\/g, '/');
444459
}
460+
461+
private _updateCommandLineForTypescriptLibPackage(
462+
commandLine: ts.ParsedCommandLine,
463+
options: IExtractorOptions
464+
): void {
465+
if (options.typescriptLibPackagePath) {
466+
commandLine.options.noLib = true;
467+
const compilerLibDirectory: string = path.join(options.typescriptLibPackagePath, 'lib');
468+
469+
let foundBaseLib: boolean = false;
470+
const filesToAdd: string[] = [];
471+
for (const libFilename of commandLine.options.lib || []) {
472+
if (libFilename === 'lib.d.ts') {
473+
// Ignore the default lib - it'll get added later
474+
continue;
475+
}
476+
477+
if (libFilename === 'lib.es5.d.ts' || libFilename === 'lib.es6.d.ts') {
478+
foundBaseLib = true;
479+
}
480+
481+
const libPath: string = path.join(compilerLibDirectory, libFilename.toLowerCase());
482+
if (!FileSystem.exists(libPath)) {
483+
throw new Error(`lib ${libFilename} does not exist in the compiler specified in typescriptLibPackage`);
484+
}
485+
486+
filesToAdd.push(libPath);
487+
}
488+
489+
if (!foundBaseLib) {
490+
// If we didn't find another version of the base lib library, include the default
491+
filesToAdd.push(path.join(compilerLibDirectory, 'lib.d.ts'));
492+
}
493+
494+
if (!commandLine.fileNames) {
495+
commandLine.fileNames = [];
496+
}
497+
498+
commandLine.fileNames.push(...filesToAdd);
499+
500+
commandLine.options.lib = undefined;
501+
}
502+
}
445503
}

common/reviews/api/api-extractor.api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ interface IExtractorOptions {
214214
compilerProgram?: ts.Program;
215215
customLogger?: Partial<ILogger>;
216216
localBuild?: boolean;
217+
// @alpha
218+
typescriptLibPackagePath?: string;
217219
}
218220

219221
// @public

0 commit comments

Comments
 (0)