mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Make type-checking cancellable.
This commit is contained in:
+32
-5
@@ -159,10 +159,10 @@ namespace ts {
|
||||
}
|
||||
};
|
||||
|
||||
function getEmitResolver(sourceFile?: SourceFile) {
|
||||
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationTokenObject) {
|
||||
// Ensure we have all the type information in place for this file so that all the
|
||||
// emitter questions of this resolver will return the right information.
|
||||
getDiagnostics(sourceFile);
|
||||
getDiagnostics(sourceFile, cancellationToken);
|
||||
return emitResolver;
|
||||
}
|
||||
|
||||
@@ -11405,8 +11405,24 @@ namespace ts {
|
||||
}
|
||||
|
||||
function checkSourceElement(node: Node): void {
|
||||
if (!node) return;
|
||||
switch (node.kind) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
let kind = node.kind;
|
||||
if (cancellationToken) {
|
||||
// Only bother checking on a few construct kinds. We don't want to be excessivly
|
||||
// hitting the cancellation token on every node we check.
|
||||
switch (kind) {
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case SyntaxKind.TypeParameter:
|
||||
return checkTypeParameter(<TypeParameterDeclaration>node);
|
||||
case SyntaxKind.Parameter:
|
||||
@@ -11661,7 +11677,18 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
|
||||
var cancellationToken: CancellationTokenObject;
|
||||
function getDiagnostics(sourceFile: SourceFile, ct: CancellationTokenObject): Diagnostic[] {
|
||||
try {
|
||||
cancellationToken = ct;
|
||||
return getDiagnosticsWorker(sourceFile);
|
||||
}
|
||||
finally {
|
||||
cancellationToken = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
|
||||
throwIfNonDiagnosticsProducing();
|
||||
if (sourceFile) {
|
||||
checkSourceFile(sourceFile);
|
||||
|
||||
@@ -799,3 +799,24 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
export class OperationCanceledException { }
|
||||
|
||||
export class CancellationTokenObject {
|
||||
public static None: CancellationTokenObject = new CancellationTokenObject(null)
|
||||
|
||||
constructor(private cancellationToken: CancellationToken) {
|
||||
}
|
||||
|
||||
public isCancellationRequested() {
|
||||
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
|
||||
}
|
||||
|
||||
public throwIfCancellationRequested(): void {
|
||||
if (this.isCancellationRequested()) {
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
-31
@@ -104,11 +104,13 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile): Diagnostic[] {
|
||||
let diagnostics = program.getSyntacticDiagnostics(sourceFile).concat(program.getGlobalDiagnostics()).concat(program.getSemanticDiagnostics(sourceFile));
|
||||
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[] {
|
||||
let diagnostics = program.getSyntacticDiagnostics(sourceFile, cancellationToken).concat(
|
||||
program.getGlobalDiagnostics(cancellationToken)).concat(
|
||||
program.getSemanticDiagnostics(sourceFile, cancellationToken));
|
||||
|
||||
if (program.getCompilerOptions().declaration) {
|
||||
diagnostics.concat(program.getDeclarationDiagnostics(sourceFile));
|
||||
diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken));
|
||||
}
|
||||
|
||||
return sortAndDeduplicateDiagnostics(diagnostics);
|
||||
@@ -230,10 +232,15 @@ namespace ts {
|
||||
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
|
||||
}
|
||||
|
||||
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback): EmitResult {
|
||||
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult {
|
||||
return runWithCancellationToken(() => emitWorker(this, sourceFile, writeFileCallback, cancellationToken));
|
||||
}
|
||||
|
||||
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationTokenObject): EmitResult {
|
||||
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
|
||||
// immediately bail out.
|
||||
if (options.noEmitOnError && getPreEmitDiagnostics(this).length > 0) {
|
||||
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
|
||||
// get any preEmit diagnostics, not just the ones
|
||||
if (options.noEmitOnError && getPreEmitDiagnostics(program, /*sourceFile:*/ undefined, cancellationToken).length > 0) {
|
||||
return { diagnostics: [], sourceMaps: undefined, emitSkipped: true };
|
||||
}
|
||||
|
||||
@@ -262,53 +269,79 @@ namespace ts {
|
||||
return filesByName.get(fileName);
|
||||
}
|
||||
|
||||
function getDiagnosticsHelper(sourceFile: SourceFile, getDiagnostics: (sourceFile: SourceFile) => Diagnostic[]): Diagnostic[] {
|
||||
function getDiagnosticsHelper(
|
||||
sourceFile: SourceFile,
|
||||
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationTokenObject) => Diagnostic[],
|
||||
cancellationToken: CancellationTokenObject): Diagnostic[] {
|
||||
if (sourceFile) {
|
||||
return getDiagnostics(sourceFile);
|
||||
return getDiagnostics(sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
let allDiagnostics: Diagnostic[] = [];
|
||||
forEach(program.getSourceFiles(), sourceFile => {
|
||||
addRange(allDiagnostics, getDiagnostics(sourceFile));
|
||||
if (cancellationToken) {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
}
|
||||
addRange(allDiagnostics, getDiagnostics(sourceFile, cancellationToken));
|
||||
});
|
||||
|
||||
return sortAndDeduplicateDiagnostics(allDiagnostics);
|
||||
}
|
||||
|
||||
function getSyntacticDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
|
||||
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile);
|
||||
function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
|
||||
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken);
|
||||
}
|
||||
|
||||
function getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
|
||||
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile);
|
||||
function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
|
||||
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken);
|
||||
}
|
||||
|
||||
function getDeclarationDiagnostics(sourceFile?: SourceFile): Diagnostic[] {
|
||||
return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile);
|
||||
function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
|
||||
return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken);
|
||||
}
|
||||
|
||||
function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
|
||||
function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
|
||||
return sourceFile.parseDiagnostics;
|
||||
}
|
||||
|
||||
function getSemanticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
|
||||
let typeChecker = getDiagnosticsProducingTypeChecker();
|
||||
function runWithCancellationToken<T>(func: () => T): T {
|
||||
try {
|
||||
return func();
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof OperationCanceledException) {
|
||||
// We were canceled while performing the operation. Because our type checker
|
||||
// might be a bad state, we need to throw it away.
|
||||
noDiagnosticsTypeChecker = undefined;
|
||||
diagnosticsProducingTypeChecker = undefined;
|
||||
}
|
||||
|
||||
Debug.assert(!!sourceFile.bindDiagnostics);
|
||||
let bindDiagnostics = sourceFile.bindDiagnostics;
|
||||
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile);
|
||||
let programDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
|
||||
|
||||
return bindDiagnostics.concat(checkDiagnostics).concat(programDiagnostics);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function getDeclarationDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
|
||||
if (!isDeclarationFile(sourceFile)) {
|
||||
let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile);
|
||||
// Don't actually write any files since we're just getting diagnostics.
|
||||
var writeFile: WriteFileCallback = () => { };
|
||||
return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile);
|
||||
}
|
||||
function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
|
||||
return runWithCancellationToken(() => {
|
||||
let typeChecker = getDiagnosticsProducingTypeChecker();
|
||||
|
||||
Debug.assert(!!sourceFile.bindDiagnostics);
|
||||
let bindDiagnostics = sourceFile.bindDiagnostics;
|
||||
let checkDiagnostics = typeChecker.getDiagnostics(sourceFile, cancellationToken);
|
||||
let programDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
|
||||
|
||||
return bindDiagnostics.concat(checkDiagnostics).concat(programDiagnostics);
|
||||
});
|
||||
}
|
||||
|
||||
function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] {
|
||||
return runWithCancellationToken(() => {
|
||||
if (!isDeclarationFile(sourceFile)) {
|
||||
let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken);
|
||||
// Don't actually write any files since we're just getting diagnostics.
|
||||
var writeFile: WriteFileCallback = () => { };
|
||||
return ts.getDeclarationDiagnostics(getEmitHost(writeFile), resolver, sourceFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getCompilerOptionsDiagnostics(): Diagnostic[] {
|
||||
|
||||
@@ -1210,13 +1210,13 @@ 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): EmitResult;
|
||||
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult;
|
||||
|
||||
getSyntacticDiagnostics(sourceFile?: SourceFile): Diagnostic[];
|
||||
getGlobalDiagnostics(): Diagnostic[];
|
||||
getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[];
|
||||
getDeclarationDiagnostics(sourceFile?: SourceFile): Diagnostic[];
|
||||
/* @internal */ getCompilerOptionsDiagnostics(): Diagnostic[];
|
||||
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
|
||||
getGlobalDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[];
|
||||
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
|
||||
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
|
||||
/* @internal */ getCompilerOptionsDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[];
|
||||
|
||||
/**
|
||||
* Gets a type checker that can be used to semantically analyze source fils in the program.
|
||||
@@ -1324,9 +1324,9 @@ namespace ts {
|
||||
getExportsOfModule(moduleSymbol: Symbol): Symbol[];
|
||||
|
||||
// Should not be called directly. Should only be accessed through the Program instance.
|
||||
/* @internal */ getDiagnostics(sourceFile?: SourceFile): Diagnostic[];
|
||||
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[];
|
||||
/* @internal */ getGlobalDiagnostics(): Diagnostic[];
|
||||
/* @internal */ getEmitResolver(sourceFile?: SourceFile): EmitResolver;
|
||||
/* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): EmitResolver;
|
||||
|
||||
/* @internal */ getNodeCount(): number;
|
||||
/* @internal */ getIdentifierCount(): number;
|
||||
|
||||
@@ -49,7 +49,6 @@ if (testConfigFile !== '') {
|
||||
if (!option) {
|
||||
continue;
|
||||
}
|
||||
ts.sys.write("Option: " + option + "\r\n");
|
||||
switch (option) {
|
||||
case 'compiler':
|
||||
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
|
||||
|
||||
@@ -1610,26 +1610,6 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
export class OperationCanceledException { }
|
||||
|
||||
export class CancellationTokenObject {
|
||||
|
||||
public static None: CancellationTokenObject = new CancellationTokenObject(null)
|
||||
|
||||
constructor(private cancellationToken: CancellationToken) {
|
||||
}
|
||||
|
||||
public isCancellationRequested() {
|
||||
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
|
||||
}
|
||||
|
||||
public throwIfCancellationRequested(): void {
|
||||
if (this.isCancellationRequested()) {
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache host information about scrip Should be refreshed
|
||||
// at each language service public entry point, since we don't know when
|
||||
// set of scripts handled by the host changes.
|
||||
@@ -2600,7 +2580,7 @@ namespace ts {
|
||||
function getSyntacticDiagnostics(fileName: string) {
|
||||
synchronizeHostData();
|
||||
|
||||
return program.getSyntacticDiagnostics(getValidSourceFile(fileName));
|
||||
return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2622,13 +2602,13 @@ namespace ts {
|
||||
// Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file.
|
||||
// Therefore only get diagnostics for given file.
|
||||
|
||||
let semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile);
|
||||
let semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken);
|
||||
if (!program.getCompilerOptions().declaration) {
|
||||
return semanticDiagnostics;
|
||||
}
|
||||
|
||||
// If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface
|
||||
let declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile);
|
||||
let declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken);
|
||||
return concatenate(semanticDiagnostics, declarationDiagnostics);
|
||||
}
|
||||
|
||||
@@ -2790,7 +2770,7 @@ namespace ts {
|
||||
|
||||
function getCompilerOptionsDiagnostics() {
|
||||
synchronizeHostData();
|
||||
return program.getGlobalDiagnostics();
|
||||
return program.getGlobalDiagnostics(cancellationToken);
|
||||
}
|
||||
|
||||
/// Completion
|
||||
@@ -5732,7 +5712,7 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
let emitOutput = program.emit(sourceFile, writeFile);
|
||||
let emitOutput = program.emit(sourceFile, writeFile, cancellationToken);
|
||||
|
||||
return {
|
||||
outputFiles,
|
||||
|
||||
Reference in New Issue
Block a user