Skip to content

Commit baea866

Browse files
committed
Redesign of Extractor class to use the new ExtractorConfig, and introduce CompilerState and ExtractorResult classes
1 parent 8c29fe8 commit baea866

8 files changed

Lines changed: 483 additions & 480 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import * as path from 'path';
5+
import * as ts from 'typescript';
6+
import colors = require('colors');
7+
8+
import {
9+
JsonFile,
10+
FileSystem
11+
} from '@microsoft/node-core-library';
12+
13+
import { ExtractorConfig } from './ExtractorConfig';
14+
import { IExtractorInvokeOptions } from './Extractor';
15+
import { TypeScriptMessageFormatter } from '../analyzer/TypeScriptMessageFormatter';
16+
17+
/**
18+
* This class represents the TypeScript compiler state. This allows an optimization where multiple invocations
19+
* of API Extractor can reuse the same TypeScript compiler analysis.
20+
*
21+
* @public
22+
*/
23+
export class CompilerState {
24+
/**
25+
* The TypeScript compiler's `Program` object, which represents a complete scope of analysis.
26+
*/
27+
public readonly program: ts.Program;
28+
29+
private constructor(properties: CompilerState) {
30+
this.program = properties.program;
31+
}
32+
33+
/**
34+
* Create a compiler state for use with the specified `IExtractorInvokeOptions`.
35+
*/
36+
public static create(extractorConfig: ExtractorConfig, options?: IExtractorInvokeOptions): CompilerState {
37+
38+
let tsconfig: {} | undefined = extractorConfig.overrideTsconfig;
39+
if (!tsconfig) {
40+
// If it wasn't overridden, then load it from disk
41+
tsconfig = JsonFile.load(path.join(extractorConfig.rootFolder, 'tsconfig.json'));
42+
}
43+
44+
const commandLine: ts.ParsedCommandLine = ts.parseJsonConfigFileContent(
45+
tsconfig,
46+
ts.sys,
47+
extractorConfig.rootFolder
48+
);
49+
50+
if (!commandLine.options.skipLibCheck && extractorConfig.skipLibCheck) {
51+
commandLine.options.skipLibCheck = true;
52+
console.log(colors.cyan(
53+
'API Extractor was invoked with skipLibCheck. This is not recommended and may cause ' +
54+
'incorrect type analysis.'
55+
));
56+
}
57+
58+
CompilerState._updateCommandLineForTypescriptPackage(commandLine, options);
59+
60+
// Append the mainEntryPointFile and remove any non-declaration files from the list
61+
const analysisFilePaths: string[] = CompilerState._generateFilePathsForAnalysis(
62+
commandLine.fileNames.concat(extractorConfig.mainEntryPointFile)
63+
);
64+
65+
const program: ts.Program = ts.createProgram(analysisFilePaths, commandLine.options);
66+
67+
if (commandLine.errors.length > 0) {
68+
const errorText: string = TypeScriptMessageFormatter.format(commandLine.errors[0].messageText);
69+
throw new Error(`Error parsing tsconfig.json content: ${errorText}`);
70+
}
71+
72+
return new CompilerState({
73+
program
74+
});
75+
}
76+
77+
/**
78+
* Given a list of absolute file paths, return a list containing only the declaration
79+
* files. Duplicates are also eliminated.
80+
*
81+
* @remarks
82+
* The tsconfig.json settings specify the compiler's input (a set of *.ts source files,
83+
* plus some *.d.ts declaration files used for legacy typings). However API Extractor
84+
* analyzes the compiler's output (a set of *.d.ts entry point files, plus any legacy
85+
* typings). This requires API Extractor to generate a special file list when it invokes
86+
* the compiler.
87+
*
88+
* Duplicates are removed so that entry points can be appended without worrying whether they
89+
* may already appear in the tsconfig.json file list.
90+
*/
91+
private static _generateFilePathsForAnalysis(inputFilePaths: string[]): string[] {
92+
const analysisFilePaths: string[] = [];
93+
94+
const seenFiles: Set<string> = new Set<string>();
95+
96+
for (const inputFilePath of inputFilePaths) {
97+
const inputFileToUpper: string = inputFilePath.toUpperCase();
98+
if (!seenFiles.has(inputFileToUpper)) {
99+
seenFiles.add(inputFileToUpper);
100+
101+
if (!path.isAbsolute(inputFilePath)) {
102+
throw new Error('Input file is not an absolute path: ' + inputFilePath);
103+
}
104+
105+
if (ExtractorConfig.hasDtsFileExtension(inputFilePath)) {
106+
analysisFilePaths.push(inputFilePath);
107+
}
108+
}
109+
}
110+
111+
return analysisFilePaths;
112+
}
113+
114+
/**
115+
* Update the parsed command line to use paths from the specified TS compiler folder, if
116+
* a TS compiler folder is specified.
117+
*/
118+
private static _updateCommandLineForTypescriptPackage(
119+
commandLine: ts.ParsedCommandLine,
120+
options?: IExtractorInvokeOptions
121+
): void {
122+
const DEFAULT_BUILTIN_LIBRARY: string = 'lib.d.ts';
123+
const OTHER_BUILTIN_LIBRARIES: string[] = ['lib.es5.d.ts', 'lib.es6.d.ts'];
124+
125+
if (options && options.typescriptCompilerFolder) {
126+
commandLine.options.noLib = true;
127+
const compilerLibFolder: string = path.join(options.typescriptCompilerFolder, 'lib');
128+
129+
let foundBaseLib: boolean = false;
130+
const filesToAdd: string[] = [];
131+
for (const libFilename of commandLine.options.lib || []) {
132+
if (libFilename === DEFAULT_BUILTIN_LIBRARY) {
133+
// Ignore the default lib - it'll get added later
134+
continue;
135+
}
136+
137+
if (OTHER_BUILTIN_LIBRARIES.indexOf(libFilename) !== -1) {
138+
foundBaseLib = true;
139+
}
140+
141+
const libPath: string = path.join(compilerLibFolder, libFilename);
142+
if (!FileSystem.exists(libPath)) {
143+
throw new Error(`lib ${libFilename} does not exist in the compiler specified in typescriptLibPackage`);
144+
}
145+
146+
filesToAdd.push(libPath);
147+
}
148+
149+
if (!foundBaseLib) {
150+
// If we didn't find another version of the base lib library, include the default
151+
filesToAdd.push(path.join(compilerLibFolder, 'lib.d.ts'));
152+
}
153+
154+
if (!commandLine.fileNames) {
155+
commandLine.fileNames = [];
156+
}
157+
158+
commandLine.fileNames.push(...filesToAdd);
159+
160+
commandLine.options.lib = undefined;
161+
}
162+
}
163+
164+
}

0 commit comments

Comments
 (0)