Skip to content

Commit 5912662

Browse files
authored
Merge pull request microsoft#1206 from Microsoft/octogonz/ae-config-file
[api-extractor] Redesign api-extractor.json and the Extractor API
2 parents 9f41072 + 0e6907d commit 5912662

69 files changed

Lines changed: 2397 additions & 1533 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api-extractor/src/analyzer/Span.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ export class SpanModification {
1414
* @remarks
1515
* Also, the modify() operation will not recurse into these spans.
1616
*/
17-
public omitChildren: boolean;
17+
public omitChildren: boolean = false;
1818

1919
/**
2020
* If true, then the Span.separator will be removed from the Span.getModifiedText() output.
2121
*/
22-
public omitSeparatorAfter: boolean;
22+
public omitSeparatorAfter: boolean = false;
2323

2424
/**
2525
* If true, then Span.getModifiedText() will sort the immediate children according to their Span.sortKey
2626
* property. The separators will also be fixed up to ensure correct indentation. If the Span.sortKey is undefined
2727
* for some items, those items will not be moved, i.e. their array indexes will be unchanged.
2828
*/
29-
public sortChildren: boolean;
29+
public sortChildren: boolean = false;
3030

3131
/**
3232
* Used if the parent span has Span.sortChildren=true.
@@ -133,7 +133,7 @@ export class Span {
133133

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

0 commit comments

Comments
 (0)