Skip to content

Commit

Permalink
Add APIs for enabling CompileOnSave on tsserver (#9837)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
zhengbli authored Aug 23, 2016
1 parent d736db3 commit a082857
Show file tree
Hide file tree
Showing 19 changed files with 4,390 additions and 71 deletions.
18 changes: 17 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
/// <reference path="scanner.ts"/>

namespace ts {
/* @internal */
export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" };
/* @internal */
export const optionDeclarations: CommandLineOption[] = [
{
name: "charset",
type: "string",
},
compileOnSaveCommandLineOption,
{
name: "declaration",
shortName: "d",
Expand Down Expand Up @@ -808,14 +811,16 @@ namespace ts {
options.configFilePath = configFileName;

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

return {
options,
fileNames,
typingOptions,
raw: json,
errors,
wildcardDirectories
wildcardDirectories,
compileOnSave
};

function getFileNames(errors: Diagnostic[]): ExpandResult {
Expand Down Expand Up @@ -870,6 +875,17 @@ namespace ts {
}
}

export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean {
if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) {
return false;
}
const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption["compileOnSave"], basePath, errors);
if (typeof result === "boolean" && result) {
return result;
}
return false;
}

export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } {
const errors: Diagnostic[] = [];
const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName);
Expand Down
24 changes: 20 additions & 4 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// <reference path="types.ts"/>
/// <reference path="types.ts"/>
/// <reference path="performance.ts" />


Expand Down Expand Up @@ -47,6 +47,7 @@ namespace ts {
contains,
remove,
forEachValue: forEachValueInMap,
getKeys,
clear,
};

Expand All @@ -56,6 +57,14 @@ namespace ts {
}
}

function getKeys() {
const keys: Path[] = [];
for (const key in files) {
keys.push(<Path>key);
}
return keys;
}

// path should already be well-formed so it does not need to be normalized
function get(path: Path): T {
return files[toKey(path)];
Expand Down Expand Up @@ -311,18 +320,25 @@ namespace ts {
* @param array A sorted array whose first element must be no larger than number
* @param number The value to be searched for in the array.
*/
export function binarySearch(array: number[], value: number): number {
export function binarySearch<T>(array: T[], value: T, comparer?: (v1: T, v2: T) => number): number {
if (!array || array.length === 0) {
return -1;
}

let low = 0;
let high = array.length - 1;
comparer = comparer !== undefined
? comparer
: (v1, v2) => (v1 < v2 ? -1 : (v1 > v2 ? 1 : 0));

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

if (midValue === value) {
if (comparer(midValue, value) === 0) {
return middle;
}
else if (midValue > value) {
else if (comparer(midValue, value) > 0) {
high = middle - 1;
}
else {
Expand Down
54 changes: 29 additions & 25 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// <reference path="checker.ts"/>
/// <reference path="checker.ts"/>
/// <reference path="sourcemap.ts" />
/// <reference path="declarationEmitter.ts"/>

Expand Down Expand Up @@ -336,7 +336,7 @@ namespace ts {
}

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

const emitJavaScript = createFileEmitter();
forEachExpectedEmitFile(host, emitFile, targetSourceFile);
forEachExpectedEmitFile(host, emitFile, targetSourceFile, emitOnlyDtsFiles);

return {
emitSkipped,
Expand Down Expand Up @@ -1615,7 +1615,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
else if (declaration.kind === SyntaxKind.ImportSpecifier) {
// Identifier references named import
write(getGeneratedNameForNode(<ImportDeclaration>declaration.parent.parent.parent));
const name = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>declaration).name;
const name = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>declaration).name;
const identifier = getTextOfNodeFromSourceText(currentText, name);
if (languageVersion === ScriptTarget.ES3 && identifier === "default") {
write('["default"]');
Expand Down Expand Up @@ -3254,19 +3254,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
write("var ");
let seen: Map<string>;
for (const id of convertedLoopState.hoistedLocalVariables) {
// Don't initialize seen unless we have at least one element.
// Emit a comma to separate for all but the first element.
if (!seen) {
// Don't initialize seen unless we have at least one element.
// Emit a comma to separate for all but the first element.
if (!seen) {
seen = createMap<string>();
}
else {
write(", ");
}
}
else {
write(", ");
}

if (!(id.text in seen)) {
emit(id);
seen[id.text] = id.text;
}
emit(id);
seen[id.text] = id.text;
}
}
write(";");
writeLine();
Expand Down Expand Up @@ -7415,7 +7415,7 @@ const _super = (function (geti, seti) {
// - import equals declarations that import external modules are not emitted
continue;
}
// fall-though for import declarations that import internal modules
// fall-though for import declarations that import internal modules
default:
writeLine();
emit(statement);
Expand Down Expand Up @@ -8364,24 +8364,28 @@ const _super = (function (geti, seti) {
}
}

function emitFile({ jsFilePath, sourceMapFilePath, declarationFilePath}: { jsFilePath: string, sourceMapFilePath: string, declarationFilePath: string },
function emitFile({ jsFilePath, sourceMapFilePath, declarationFilePath }: EmitFileNames,
sourceFiles: SourceFile[], isBundledEmit: boolean) {
// Make sure not to write js File and source map file if any of them cannot be written
if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) {
emitJavaScript(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
}
else {
emitSkipped = true;
if (!emitOnlyDtsFiles) {
// Make sure not to write js File and source map file if any of them cannot be written
if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) {
emitJavaScript(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
}
else {
emitSkipped = true;
}
}

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

if (!emitSkipped && emittedFilesList) {
emittedFilesList.push(jsFilePath);
if (sourceMapFilePath) {
emittedFilesList.push(sourceMapFilePath);
if (!emitOnlyDtsFiles) {
emittedFilesList.push(jsFilePath);
if (sourceMapFilePath) {
emittedFilesList.push(sourceMapFilePath);
}
}
if (declarationFilePath) {
emittedFilesList.push(declarationFilePath);
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// <reference path="utilities.ts"/>
/// <reference path="utilities.ts"/>
/// <reference path="scanner.ts"/>

namespace ts {
Expand Down
9 changes: 5 additions & 4 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,15 +774,15 @@ namespace ts {
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
}

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

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

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

if (options.noEmit) {
Expand Down Expand Up @@ -827,7 +827,8 @@ namespace ts {
const emitResult = emitFiles(
emitResolver,
getEmitHost(writeFileCallback),
sourceFile);
sourceFile,
emitOnlyDtsFiles);

performance.mark("afterEmit");
performance.measure("Emit", "beforeEmit", "afterEmit");
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace ts {
remove(fileName: Path): void;

forEachValue(f: (key: Path, v: T) => void): void;
getKeys(): Path[];
clear(): void;
}

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

getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[];
getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[];
Expand Down Expand Up @@ -2736,6 +2737,7 @@ namespace ts {
raw?: any;
errors: Diagnostic[];
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
compileOnSave?: boolean;
}

export const enum WatchDirectoryFlags {
Expand Down
18 changes: 9 additions & 9 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// <reference path="sys.ts" />
/// <reference path="sys.ts" />

/* @internal */
namespace ts {
Expand Down Expand Up @@ -2218,12 +2218,10 @@ namespace ts {
const options = host.getCompilerOptions();
const outputDir = options.declarationDir || options.outDir; // Prefer declaration folder if specified

if (options.declaration) {
const path = outputDir
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
: sourceFile.fileName;
return removeFileExtension(path) + ".d.ts";
}
const path = outputDir
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
: sourceFile.fileName;
return removeFileExtension(path) + ".d.ts";
}

export interface EmitFileNames {
Expand All @@ -2234,7 +2232,8 @@ namespace ts {

export function forEachExpectedEmitFile(host: EmitHost,
action: (emitFileNames: EmitFileNames, sourceFiles: SourceFile[], isBundledEmit: boolean) => void,
targetSourceFile?: SourceFile) {
targetSourceFile?: SourceFile,
emitOnlyDtsFiles?: boolean) {
const options = host.getCompilerOptions();
// Emit on each source file
if (options.outFile || options.out) {
Expand Down Expand Up @@ -2267,10 +2266,11 @@ namespace ts {
}
}
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, extension);
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
const emitFileNames: EmitFileNames = {
jsFilePath,
sourceMapFilePath: getSourceMapFilePath(jsFilePath, options),
declarationFilePath: !isSourceFileJavaScript(sourceFile) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined
declarationFilePath
};
action(emitFileNames, [sourceFile], /*isBundledEmit*/false);
}
Expand Down
Loading

0 comments on commit a082857

Please sign in to comment.