Skip to content

Commit a082857

Browse files
author
Zhengbo Li
authored
Add APIs for enabling CompileOnSave on tsserver (microsoft#9837)
* Add API to get only the emited declarations output * Add nonModuleBuilder * Add basic tests for CompileOnSaveAffectedFileList API * Add API for compile single file * Avoid invoking project.languageService directly * Add API to query if compileOnSave is enabled for a project * Seperate check and emit signatures * Use Path type for internal file name matching and simplifying builder logic * Always return cascaded affected list * Correct the tsconfig file in compileOnSave tests Also move the CompileOnSave option out of compilerOptions * Reduce string to path conversion
1 parent d736db3 commit a082857

19 files changed

Lines changed: 4390 additions & 71 deletions

src/compiler/commandLineParser.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
/// <reference path="scanner.ts"/>
66

77
namespace ts {
8+
/* @internal */
9+
export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" };
810
/* @internal */
911
export const optionDeclarations: CommandLineOption[] = [
1012
{
1113
name: "charset",
1214
type: "string",
1315
},
16+
compileOnSaveCommandLineOption,
1417
{
1518
name: "declaration",
1619
shortName: "d",
@@ -808,14 +811,16 @@ namespace ts {
808811
options.configFilePath = configFileName;
809812

810813
const { fileNames, wildcardDirectories } = getFileNames(errors);
814+
const compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
811815

812816
return {
813817
options,
814818
fileNames,
815819
typingOptions,
816820
raw: json,
817821
errors,
818-
wildcardDirectories
822+
wildcardDirectories,
823+
compileOnSave
819824
};
820825

821826
function getFileNames(errors: Diagnostic[]): ExpandResult {
@@ -870,6 +875,17 @@ namespace ts {
870875
}
871876
}
872877

878+
export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean {
879+
if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) {
880+
return false;
881+
}
882+
const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption["compileOnSave"], basePath, errors);
883+
if (typeof result === "boolean" && result) {
884+
return result;
885+
}
886+
return false;
887+
}
888+
873889
export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } {
874890
const errors: Diagnostic[] = [];
875891
const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName);

src/compiler/core.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// <reference path="types.ts"/>
1+
/// <reference path="types.ts"/>
22
/// <reference path="performance.ts" />
33

44

@@ -47,6 +47,7 @@ namespace ts {
4747
contains,
4848
remove,
4949
forEachValue: forEachValueInMap,
50+
getKeys,
5051
clear,
5152
};
5253

@@ -56,6 +57,14 @@ namespace ts {
5657
}
5758
}
5859

60+
function getKeys() {
61+
const keys: Path[] = [];
62+
for (const key in files) {
63+
keys.push(<Path>key);
64+
}
65+
return keys;
66+
}
67+
5968
// path should already be well-formed so it does not need to be normalized
6069
function get(path: Path): T {
6170
return files[toKey(path)];
@@ -311,18 +320,25 @@ namespace ts {
311320
* @param array A sorted array whose first element must be no larger than number
312321
* @param number The value to be searched for in the array.
313322
*/
314-
export function binarySearch(array: number[], value: number): number {
323+
export function binarySearch<T>(array: T[], value: T, comparer?: (v1: T, v2: T) => number): number {
324+
if (!array || array.length === 0) {
325+
return -1;
326+
}
327+
315328
let low = 0;
316329
let high = array.length - 1;
330+
comparer = comparer !== undefined
331+
? comparer
332+
: (v1, v2) => (v1 < v2 ? -1 : (v1 > v2 ? 1 : 0));
317333

318334
while (low <= high) {
319335
const middle = low + ((high - low) >> 1);
320336
const midValue = array[middle];
321337

322-
if (midValue === value) {
338+
if (comparer(midValue, value) === 0) {
323339
return middle;
324340
}
325-
else if (midValue > value) {
341+
else if (comparer(midValue, value) > 0) {
326342
high = middle - 1;
327343
}
328344
else {

src/compiler/emitter.ts

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// <reference path="checker.ts"/>
1+
/// <reference path="checker.ts"/>
22
/// <reference path="sourcemap.ts" />
33
/// <reference path="declarationEmitter.ts"/>
44

@@ -336,7 +336,7 @@ namespace ts {
336336
}
337337

338338
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
339-
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult {
339+
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean): EmitResult {
340340
// emit output for the __extends helper function
341341
const extendsHelper = `
342342
var __extends = (this && this.__extends) || function (d, b) {
@@ -396,7 +396,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
396396
const newLine = host.getNewLine();
397397

398398
const emitJavaScript = createFileEmitter();
399-
forEachExpectedEmitFile(host, emitFile, targetSourceFile);
399+
forEachExpectedEmitFile(host, emitFile, targetSourceFile, emitOnlyDtsFiles);
400400

401401
return {
402402
emitSkipped,
@@ -1615,7 +1615,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
16151615
else if (declaration.kind === SyntaxKind.ImportSpecifier) {
16161616
// Identifier references named import
16171617
write(getGeneratedNameForNode(<ImportDeclaration>declaration.parent.parent.parent));
1618-
const name = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>declaration).name;
1618+
const name = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>declaration).name;
16191619
const identifier = getTextOfNodeFromSourceText(currentText, name);
16201620
if (languageVersion === ScriptTarget.ES3 && identifier === "default") {
16211621
write('["default"]');
@@ -3254,19 +3254,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32543254
write("var ");
32553255
let seen: Map<string>;
32563256
for (const id of convertedLoopState.hoistedLocalVariables) {
3257-
// Don't initialize seen unless we have at least one element.
3258-
// Emit a comma to separate for all but the first element.
3259-
if (!seen) {
3257+
// Don't initialize seen unless we have at least one element.
3258+
// Emit a comma to separate for all but the first element.
3259+
if (!seen) {
32603260
seen = createMap<string>();
3261-
}
3262-
else {
3263-
write(", ");
3264-
}
3261+
}
3262+
else {
3263+
write(", ");
3264+
}
32653265

32663266
if (!(id.text in seen)) {
3267-
emit(id);
3268-
seen[id.text] = id.text;
3269-
}
3267+
emit(id);
3268+
seen[id.text] = id.text;
3269+
}
32703270
}
32713271
write(";");
32723272
writeLine();
@@ -7415,7 +7415,7 @@ const _super = (function (geti, seti) {
74157415
// - import equals declarations that import external modules are not emitted
74167416
continue;
74177417
}
7418-
// fall-though for import declarations that import internal modules
7418+
// fall-though for import declarations that import internal modules
74197419
default:
74207420
writeLine();
74217421
emit(statement);
@@ -8364,24 +8364,28 @@ const _super = (function (geti, seti) {
83648364
}
83658365
}
83668366

8367-
function emitFile({ jsFilePath, sourceMapFilePath, declarationFilePath}: { jsFilePath: string, sourceMapFilePath: string, declarationFilePath: string },
8367+
function emitFile({ jsFilePath, sourceMapFilePath, declarationFilePath }: EmitFileNames,
83688368
sourceFiles: SourceFile[], isBundledEmit: boolean) {
8369-
// Make sure not to write js File and source map file if any of them cannot be written
8370-
if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) {
8371-
emitJavaScript(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
8372-
}
8373-
else {
8374-
emitSkipped = true;
8369+
if (!emitOnlyDtsFiles) {
8370+
// Make sure not to write js File and source map file if any of them cannot be written
8371+
if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) {
8372+
emitJavaScript(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
8373+
}
8374+
else {
8375+
emitSkipped = true;
8376+
}
83758377
}
83768378

83778379
if (declarationFilePath) {
83788380
emitSkipped = writeDeclarationFile(declarationFilePath, sourceFiles, isBundledEmit, host, resolver, emitterDiagnostics) || emitSkipped;
83798381
}
83808382

83818383
if (!emitSkipped && emittedFilesList) {
8382-
emittedFilesList.push(jsFilePath);
8383-
if (sourceMapFilePath) {
8384-
emittedFilesList.push(sourceMapFilePath);
8384+
if (!emitOnlyDtsFiles) {
8385+
emittedFilesList.push(jsFilePath);
8386+
if (sourceMapFilePath) {
8387+
emittedFilesList.push(sourceMapFilePath);
8388+
}
83858389
}
83868390
if (declarationFilePath) {
83878391
emittedFilesList.push(declarationFilePath);

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// <reference path="utilities.ts"/>
1+
/// <reference path="utilities.ts"/>
22
/// <reference path="scanner.ts"/>
33

44
namespace ts {

src/compiler/program.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -774,15 +774,15 @@ namespace ts {
774774
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
775775
}
776776

777-
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult {
778-
return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken));
777+
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult {
778+
return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles));
779779
}
780780

781781
function isEmitBlocked(emitFileName: string): boolean {
782782
return hasEmitBlockingDiagnostics.contains(toPath(emitFileName, currentDirectory, getCanonicalFileName));
783783
}
784784

785-
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken): EmitResult {
785+
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult {
786786
let declarationDiagnostics: Diagnostic[] = [];
787787

788788
if (options.noEmit) {
@@ -827,7 +827,8 @@ namespace ts {
827827
const emitResult = emitFiles(
828828
emitResolver,
829829
getEmitHost(writeFileCallback),
830-
sourceFile);
830+
sourceFile,
831+
emitOnlyDtsFiles);
831832

832833
performance.mark("afterEmit");
833834
performance.measure("Emit", "beforeEmit", "afterEmit");

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace ts {
1919
remove(fileName: Path): void;
2020

2121
forEachValue(f: (key: Path, v: T) => void): void;
22+
getKeys(): Path[];
2223
clear(): void;
2324
}
2425

@@ -1755,7 +1756,7 @@ namespace ts {
17551756
* used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter
17561757
* will be invoked when writing the JavaScript and declaration files.
17571758
*/
1758-
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
1759+
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult;
17591760

17601761
getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[];
17611762
getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[];
@@ -2736,6 +2737,7 @@ namespace ts {
27362737
raw?: any;
27372738
errors: Diagnostic[];
27382739
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
2740+
compileOnSave?: boolean;
27392741
}
27402742

27412743
export const enum WatchDirectoryFlags {

src/compiler/utilities.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// <reference path="sys.ts" />
1+
/// <reference path="sys.ts" />
22

33
/* @internal */
44
namespace ts {
@@ -2218,12 +2218,10 @@ namespace ts {
22182218
const options = host.getCompilerOptions();
22192219
const outputDir = options.declarationDir || options.outDir; // Prefer declaration folder if specified
22202220

2221-
if (options.declaration) {
2222-
const path = outputDir
2223-
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
2224-
: sourceFile.fileName;
2225-
return removeFileExtension(path) + ".d.ts";
2226-
}
2221+
const path = outputDir
2222+
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
2223+
: sourceFile.fileName;
2224+
return removeFileExtension(path) + ".d.ts";
22272225
}
22282226

22292227
export interface EmitFileNames {
@@ -2234,7 +2232,8 @@ namespace ts {
22342232

22352233
export function forEachExpectedEmitFile(host: EmitHost,
22362234
action: (emitFileNames: EmitFileNames, sourceFiles: SourceFile[], isBundledEmit: boolean) => void,
2237-
targetSourceFile?: SourceFile) {
2235+
targetSourceFile?: SourceFile,
2236+
emitOnlyDtsFiles?: boolean) {
22382237
const options = host.getCompilerOptions();
22392238
// Emit on each source file
22402239
if (options.outFile || options.out) {
@@ -2267,10 +2266,11 @@ namespace ts {
22672266
}
22682267
}
22692268
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, extension);
2269+
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
22702270
const emitFileNames: EmitFileNames = {
22712271
jsFilePath,
22722272
sourceMapFilePath: getSourceMapFilePath(jsFilePath, options),
2273-
declarationFilePath: !isSourceFileJavaScript(sourceFile) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined
2273+
declarationFilePath
22742274
};
22752275
action(emitFileNames, [sourceFile], /*isBundledEmit*/false);
22762276
}

0 commit comments

Comments
 (0)