From 958a423142dad45c4e9f51e461d383f51cbe8ed5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 08:56:48 -0700 Subject: [PATCH 01/14] Make classification cancellable. --- src/services/services.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index bab5b7707ef..188e711ed9f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5986,6 +5986,26 @@ namespace ts { return convertClassifications(getEncodedSemanticClassifications(fileName, span)); } + function checkForClassificationCancellation(kind: SyntaxKind) { + // We don't want to actually call back into our host on every node to find out if we've + // been canceled. That would be an enormous amount of chattyness, along with the all + // the overhead of marshalling the data to/from the host. So instead we pick a few + // reasonable node kinds to bother checking on. These node kinds represent high level + // constructs that we would expect to see commonly, but just at a far less frequent + // interval. + // + // For example, in checker.ts (around 750k) we only have around 600 of these constructs. + // That means we're calling back into the host around every 1.2k of the file we process. + // Lib.d.ts has similar numbers. + switch (kind) { + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + cancellationToken.throwIfCancellationRequested(); + } + } + function getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications { synchronizeHostData(); @@ -6053,7 +6073,10 @@ namespace ts { function processNode(node: Node) { // Only walk into nodes that intersect the requested span. if (node && textSpanIntersectsWith(span, node.getFullStart(), node.getFullWidth())) { - if (node.kind === SyntaxKind.Identifier && !nodeIsMissing(node)) { + let kind = node.kind; + checkForClassificationCancellation(kind); + + if (kind === SyntaxKind.Identifier && !nodeIsMissing(node)) { let identifier = node; // Only bother calling into the typechecker if this is an identifier that @@ -6405,6 +6428,8 @@ namespace ts { // Ignore nodes that don't intersect the original span to classify. if (textSpanIntersectsWith(span, element.getFullStart(), element.getFullWidth())) { + checkForClassificationCancellation(element.kind); + let children = element.getChildren(sourceFile); for (let child of children) { if (isToken(child)) { From 1a96a146ed7bbb1af2ad568d4eacb1809525e6db Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 10:52:19 -0700 Subject: [PATCH 02/14] Make type-checking cancellable. --- src/compiler/checker.ts | 37 +++++++++++++--- src/compiler/core.ts | 21 +++++++++ src/compiler/program.ts | 95 +++++++++++++++++++++++++++------------- src/compiler/types.ts | 16 +++---- src/harness/runner.ts | 1 - src/services/services.ts | 30 +++---------- 6 files changed, 130 insertions(+), 70 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index efc3e32166e..c550c703336 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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(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); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ced477eeeec..49371dbccd2 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -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(); + } + } + } +} \ No newline at end of file diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 800e2238a54..df976f17ae9 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -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(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[] { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c39426a2e22..b230b2c5feb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 42c2d5667c4..1e9c6470421 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -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)); diff --git a/src/services/services.ts b/src/services/services.ts index bed1f9b6bb9..86540ccd3e5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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, From df13b8ffd199223881daf9231cdd63ec6f6b716f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 11:43:16 -0700 Subject: [PATCH 03/14] Update comment. --- src/compiler/checker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3fd917bc00e..0fcc478d2b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11684,6 +11684,9 @@ namespace ts { var cancellationToken: CancellationTokenObject; function getDiagnostics(sourceFile: SourceFile, ct: CancellationTokenObject): Diagnostic[] { try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. cancellationToken = ct; return getDiagnosticsWorker(sourceFile); } From c9c0f3a4adc2bbe031c402ab70f6707828ff8e35 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 12:04:26 -0700 Subject: [PATCH 04/14] PR feedback. --- src/compiler/checker.ts | 6 +++--- src/compiler/core.ts | 21 ------------------- src/compiler/program.ts | 22 ++++++++++---------- src/compiler/types.ts | 22 +++++++++++--------- src/harness/fourslash.ts | 10 ++++----- src/harness/harnessLanguageService.ts | 29 ++++++++++++--------------- src/services/navigateTo.ts | 2 +- src/services/services.ts | 21 ++++++++++++++++++- src/services/shims.ts | 4 ++-- src/services/signatureHelp.ts | 2 +- 10 files changed, 69 insertions(+), 70 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0fcc478d2b1..8fd7f1a75d9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -167,7 +167,7 @@ namespace ts { return checker; - function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationTokenObject) { + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { // 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, cancellationToken); @@ -11681,8 +11681,8 @@ namespace ts { } } - var cancellationToken: CancellationTokenObject; - function getDiagnostics(sourceFile: SourceFile, ct: CancellationTokenObject): Diagnostic[] { + var cancellationToken: CancellationToken; + function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { try { // Record the cancellation token so it can be checked later on during checkSourceElement. // Do this in a finally block so we can ensure that it gets reset back to nothing after diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 49371dbccd2..52eae6e37c1 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -798,25 +798,4 @@ namespace ts { Debug.assert(false, message); } } -} - -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(); - } - } - } } \ No newline at end of file diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 443f5f92b49..f0277e60d0d 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -104,7 +104,7 @@ namespace ts { }; } - export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[] { + export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] { let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat( program.getSyntacticDiagnostics(sourceFile, cancellationToken), program.getGlobalDiagnostics(cancellationToken), @@ -233,11 +233,11 @@ namespace ts { return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationTokenObject): EmitResult { + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { return runWithCancellationToken(() => emitWorker(this, sourceFile, writeFileCallback, cancellationToken)); } - function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationTokenObject): EmitResult { + function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken): EmitResult { // If the noEmitOnError flag is set, then check if we have any errors so far. If so, // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we // get any preEmit diagnostics, not just the ones @@ -272,8 +272,8 @@ namespace ts { function getDiagnosticsHelper( sourceFile: SourceFile, - getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationTokenObject) => Diagnostic[], - cancellationToken: CancellationTokenObject): Diagnostic[] { + getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[], + cancellationToken: CancellationToken): Diagnostic[] { if (sourceFile) { return getDiagnostics(sourceFile, cancellationToken); } @@ -289,19 +289,19 @@ namespace ts { return sortAndDeduplicateDiagnostics(allDiagnostics); } - function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSyntacticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); } - function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); } - function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); } - function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return sourceFile.parseDiagnostics; } @@ -321,7 +321,7 @@ namespace ts { } } - function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { let typeChecker = getDiagnosticsProducingTypeChecker(); @@ -334,7 +334,7 @@ namespace ts { }); } - function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationTokenObject): Diagnostic[] { + function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return runWithCancellationToken(() => { if (!isDeclarationFile(sourceFile)) { let resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 799c291f46a..662c10f4235 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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, cancellationToken?: CancellationTokenObject): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; - getOptionsDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[]; - getGlobalDiagnostics(cancellationToken?: CancellationTokenObject): Diagnostic[]; - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[]; - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[]; - getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): Diagnostic[]; + getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; + getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): 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, cancellationToken?: CancellationTokenObject): Diagnostic[]; + /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /* @internal */ getGlobalDiagnostics(): Diagnostic[]; - /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationTokenObject): EmitResolver; + /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken): EmitResolver; /* @internal */ getNodeCount(): number; /* @internal */ getIdentifierCount(): number; @@ -2052,14 +2052,18 @@ namespace ts { verticalTab = 0x0B, // \v } + export class OperationCanceledException { } + export interface CancellationToken { isCancellationRequested(): boolean; + + /** @throws OperationCanceledException if isCancellationRequested is true */ + throwIfCancellationRequested(): void; } export interface CompilerHost { getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; getDefaultLibFileName(options: CompilerOptions): string; - getCancellationToken? (): CancellationToken; writeFile: WriteFileCallback; getCurrentDirectory(): string; getCanonicalFileName(fileName: string): string; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 5915698462c..41dda5bc9b3 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -190,14 +190,14 @@ module FourSlash { return "\nMarker: " + currentTestState.lastKnownMarker + "\nChecking: " + msg + "\n\n"; } - export class TestCancellationToken implements ts.CancellationToken { + export class TestCancellationToken implements ts.HostCancellationToken { // 0 - cancelled // >0 - not cancelled // <0 - not cancelled and value denotes number of isCancellationRequested after which token become cancelled - private static NotCancelled: number = -1; - private numberOfCallsBeforeCancellation: number = TestCancellationToken.NotCancelled; - public isCancellationRequested(): boolean { + private static NotCanceled: number = -1; + private numberOfCallsBeforeCancellation: number = TestCancellationToken.NotCanceled; + public isCancellationRequested(): boolean { if (this.numberOfCallsBeforeCancellation < 0) { return false; } @@ -216,7 +216,7 @@ module FourSlash { } public resetCancelled(): void { - this.numberOfCallsBeforeCancellation = TestCancellationToken.NotCancelled; + this.numberOfCallsBeforeCancellation = TestCancellationToken.NotCanceled; } } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 8eb77817533..6cb92df5948 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -103,14 +103,11 @@ module Harness.LanguageService { } } - class CancellationToken { - public static None: CancellationToken = new CancellationToken(null); - - constructor(private cancellationToken: ts.CancellationToken) { - } + class DefaultHostCancellationToken implements ts.HostCancellationToken { + public static Instance = new DefaultHostCancellationToken(); public isCancellationRequested() { - return this.cancellationToken && this.cancellationToken.isCancellationRequested(); + return false; } } @@ -124,8 +121,8 @@ module Harness.LanguageService { export class LanguageServiceAdapterHost { protected fileNameToScript: ts.Map = {}; - constructor(protected cancellationToken: ts.CancellationToken = CancellationToken.None, - protected settings = ts.getDefaultCompilerOptions()) { + constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, + protected settings = ts.getDefaultCompilerOptions()) { } public getNewLine(): string { @@ -173,8 +170,8 @@ module Harness.LanguageService { /// Native adapter class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost { - getCompilationSettings(): ts.CompilerOptions { return this.settings; } - getCancellationToken(): ts.CancellationToken { return this.cancellationToken; } + getCompilationSettings() { return this.settings; } + getCancellationToken() { return this.cancellationToken; } getCurrentDirectory(): string { return ""; } getDefaultLibFileName(): string { return ""; } getScriptFileNames(): string[] { return this.getFilenames(); } @@ -194,7 +191,7 @@ module Harness.LanguageService { export class NativeLanugageServiceAdapter implements LanguageServiceAdapter { private host: NativeLanguageServiceHost; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { this.host = new NativeLanguageServiceHost(cancellationToken, options); } getHost() { return this.host; } @@ -206,7 +203,7 @@ module Harness.LanguageService { /// Shim adapter class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { private nativeHost: NativeLanguageServiceHost; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { super(cancellationToken, options); this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); } @@ -218,7 +215,7 @@ module Harness.LanguageService { positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); } getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); } - getCancellationToken(): ts.CancellationToken { return this.nativeHost.getCancellationToken(); } + getCancellationToken(): ts.HostCancellationToken { return this.nativeHost.getCancellationToken(); } getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); } getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); } getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } @@ -399,7 +396,7 @@ module Harness.LanguageService { export class ShimLanugageServiceAdapter implements LanguageServiceAdapter { private host: ShimLanguageServiceHost; private factory: ts.TypeScriptServicesFactory; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { this.host = new ShimLanguageServiceHost(cancellationToken, options); this.factory = new TypeScript.Services.TypeScriptServicesFactory(); } @@ -446,7 +443,7 @@ module Harness.LanguageService { class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost { private client: ts.server.SessionClient; - constructor(cancellationToken: ts.CancellationToken, settings: ts.CompilerOptions) { + constructor(cancellationToken: ts.HostCancellationToken, settings: ts.CompilerOptions) { super(cancellationToken, settings); } @@ -575,7 +572,7 @@ module Harness.LanguageService { export class ServerLanugageServiceAdapter implements LanguageServiceAdapter { private host: SessionClientHost; private client: ts.server.SessionClient; - constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { // This is the main host that tests use to direct tests var clientHost = new SessionClientHost(cancellationToken, options); var client = new ts.server.SessionClient(clientHost); diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index a4cc7ec2ad5..bc506bc22f9 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -2,7 +2,7 @@ namespace ts.NavigateTo { type RawNavigateToItem = { name: string; fileName: string; matchKind: PatternMatchKind; isCaseSensitive: boolean; declaration: Declaration }; - export function getNavigateToItems(program: Program, cancellationToken: CancellationTokenObject, searchValue: string, maxResultCount: number): NavigateToItem[] { + export function getNavigateToItems(program: Program, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number): NavigateToItem[] { let patternMatcher = createPatternMatcher(searchValue); let rawItems: RawNavigateToItem[] = []; diff --git a/src/services/services.ts b/src/services/services.ts index f7f7dfcf6b3..efb7a61a8dd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -944,6 +944,10 @@ namespace ts { } } + export interface HostCancellationToken { + isCancellationRequested(): boolean; + } + // // Public interface of the host of a language service instance. // @@ -955,7 +959,7 @@ namespace ts { getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): IScriptSnapshot; getLocalizedDiagnosticMessages?(): any; - getCancellationToken?(): CancellationToken; + getCancellationToken?(): HostCancellationToken; getCurrentDirectory(): string; getDefaultLibFileName(options: CompilerOptions): string; log? (s: string): void; @@ -2379,6 +2383,21 @@ namespace ts { return ScriptElementKind.unknown; } + class CancellationTokenObject implements CancellationToken { + constructor(private cancellationToken: HostCancellationToken) { + } + + public isCancellationRequested() { + return this.cancellationToken && this.cancellationToken.isCancellationRequested(); + } + + public throwIfCancellationRequested(): void { + if (this.isCancellationRequested()) { + throw new OperationCanceledException(); + } + } + } + export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry = createDocumentRegistry()): LanguageService { let syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); let ruleProvider: formatting.RulesProvider; diff --git a/src/services/shims.ts b/src/services/shims.ts index 2e8b3eb774d..f56cef82f9d 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -51,7 +51,7 @@ namespace ts { getScriptVersion(fileName: string): string; getScriptSnapshot(fileName: string): ScriptSnapshotShim; getLocalizedDiagnosticMessages(): string; - getCancellationToken(): CancellationToken; + getCancellationToken(): HostCancellationToken; getCurrentDirectory(): string; getDefaultLibFileName(options: string): string; getNewLine?(): string; @@ -326,7 +326,7 @@ namespace ts { } } - public getCancellationToken(): CancellationToken { + public getCancellationToken(): HostCancellationToken { return this.shimHost.getCancellationToken(); } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index fce4e2c3025..44b022a7b12 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -178,7 +178,7 @@ namespace ts.SignatureHelp { argumentCount: number; } - export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, cancellationToken: CancellationTokenObject): SignatureHelpItems { + export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, cancellationToken: CancellationToken): SignatureHelpItems { let typeChecker = program.getTypeChecker(); // Decide whether to show signature help From 053be425c708e8fe59169d0c554160fbcb6d3b6c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Jun 2015 15:25:08 -0700 Subject: [PATCH 05/14] Add tests. --- src/harness/runner.ts | 1 - .../semanticClassificationsCancellation1.ts | 15 +++++++++++++ .../syntacticClassificationsCancellation1.ts | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/semanticClassificationsCancellation1.ts create mode 100644 tests/cases/fourslash/syntacticClassificationsCancellation1.ts diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 42c2d5667c4..1e9c6470421 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -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)); diff --git a/tests/cases/fourslash/semanticClassificationsCancellation1.ts b/tests/cases/fourslash/semanticClassificationsCancellation1.ts new file mode 100644 index 00000000000..9381bef5e86 --- /dev/null +++ b/tests/cases/fourslash/semanticClassificationsCancellation1.ts @@ -0,0 +1,15 @@ +/// + +////module M { +////} +////module N { +////} + +var c = classification; +cancellation.setCancelled(1); +verifyOperationIsCancelled(() => verify.semanticClassificationsAre()); +cancellation.resetCancelled(); + +verify.semanticClassificationsAre( + c.moduleName("M"), + c.moduleName("N")); diff --git a/tests/cases/fourslash/syntacticClassificationsCancellation1.ts b/tests/cases/fourslash/syntacticClassificationsCancellation1.ts new file mode 100644 index 00000000000..f15ce5f9984 --- /dev/null +++ b/tests/cases/fourslash/syntacticClassificationsCancellation1.ts @@ -0,0 +1,21 @@ +/// + +////module M { +////} +////module N { +////} + +var c = classification; +cancellation.setCancelled(1); +verifyOperationIsCancelled(() => verify.syntacticClassificationsAre()); +cancellation.resetCancelled(); + +verify.syntacticClassificationsAre( + c.keyword("module"), + c.moduleName("M"), + c.punctuation("{"), + c.punctuation("}"), + c.keyword("module"), + c.moduleName("N"), + c.punctuation("{"), + c.punctuation("}")); From 4082a540e245c03aa20f21efce57f918a3dd615f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 1 Jul 2015 13:25:51 -0700 Subject: [PATCH 06/14] Added checks for new identifier location in tests. --- .../cases/fourslash/completionListInObjectBindingPattern01.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern02.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern04.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern05.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern07.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern08.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern09.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern10.ts | 4 +++- .../cases/fourslash/completionListInObjectBindingPattern11.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern12.ts | 3 ++- .../cases/fourslash/completionListInObjectBindingPattern13.ts | 3 ++- 11 files changed, 23 insertions(+), 11 deletions(-) diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern01.ts b/tests/cases/fourslash/completionListInObjectBindingPattern01.ts index 128e95b8ed3..99188457e6f 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern01.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern01.ts @@ -10,4 +10,5 @@ goTo.marker(); verify.completionListContains("property1"); -verify.completionListContains("property2"); \ No newline at end of file +verify.completionListContains("property2"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern02.ts b/tests/cases/fourslash/completionListInObjectBindingPattern02.ts index 6d2b7a6b516..0cd86c8d1a6 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern02.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern02.ts @@ -10,4 +10,5 @@ goTo.marker(); verify.completionListContains("property2"); -verify.not.completionListContains("property1"); \ No newline at end of file +verify.not.completionListContains("property1"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern04.ts b/tests/cases/fourslash/completionListInObjectBindingPattern04.ts index 92b202a1b37..e6057ec0fae 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern04.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern04.ts @@ -10,4 +10,5 @@ goTo.marker(); verify.completionListContains("property1"); -verify.completionListContains("property2"); \ No newline at end of file +verify.completionListContains("property2"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern05.ts b/tests/cases/fourslash/completionListInObjectBindingPattern05.ts index d17e11810df..d0876ed6a34 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern05.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern05.ts @@ -9,4 +9,5 @@ ////var { property1/**/ } = foo; goTo.marker(); -verify.completionListContains("property1"); \ No newline at end of file +verify.completionListContains("property1"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern07.ts b/tests/cases/fourslash/completionListInObjectBindingPattern07.ts index a3015346aa3..4db3fc383d4 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern07.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern07.ts @@ -15,4 +15,5 @@ goTo.marker(); verify.completionListContains("propertyOfI_1"); verify.completionListContains("propertyOfI_2"); -verify.not.completionListContains("property2"); \ No newline at end of file +verify.not.completionListContains("property2"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern08.ts b/tests/cases/fourslash/completionListInObjectBindingPattern08.ts index 6d6be330b83..b062ca702a2 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern08.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern08.ts @@ -15,4 +15,5 @@ goTo.marker(); verify.completionListContains("propertyOfI_2"); verify.not.completionListContains("propertyOfI_1"); -verify.not.completionListContains("property2"); \ No newline at end of file +verify.not.completionListContains("property2"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern09.ts b/tests/cases/fourslash/completionListInObjectBindingPattern09.ts index 2698e78667b..61b31385b35 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern09.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern09.ts @@ -16,4 +16,5 @@ goTo.marker(); verify.completionListContains("property2"); verify.not.completionListContains("property1"); verify.not.completionListContains("propertyOfI_2"); -verify.not.completionListContains("propertyOfI_1"); \ No newline at end of file +verify.not.completionListContains("propertyOfI_1"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern10.ts b/tests/cases/fourslash/completionListInObjectBindingPattern10.ts index fcc09d1ead4..836e12fd8a3 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern10.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern10.ts @@ -17,7 +17,9 @@ verify.completionListContains("property2"); verify.not.completionListContains("property1"); verify.not.completionListContains("propertyOfI_2"); verify.not.completionListContains("propertyOfI_1"); +verify.not.completionListAllowsNewIdentifier(); goTo.marker("2"); verify.completionListContains("property1"); -verify.completionListContains("property2"); \ No newline at end of file +verify.completionListContains("property2"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern11.ts b/tests/cases/fourslash/completionListInObjectBindingPattern11.ts index 25d6651a038..57a32578026 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern11.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern11.ts @@ -10,4 +10,5 @@ goTo.marker(""); verify.completionListContains("property2"); verify.not.completionListContains("property1"); -verify.not.completionListContains("prop1"); \ No newline at end of file +verify.not.completionListContains("prop1"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern12.ts b/tests/cases/fourslash/completionListInObjectBindingPattern12.ts index f31206a21b6..59af2da38d2 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern12.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern12.ts @@ -10,4 +10,5 @@ goTo.marker(""); verify.completionListContains("property2"); -verify.not.completionListContains("property1"); \ No newline at end of file +verify.not.completionListContains("property1"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern13.ts b/tests/cases/fourslash/completionListInObjectBindingPattern13.ts index f91f9fcceed..8081d2ee23b 100644 --- a/tests/cases/fourslash/completionListInObjectBindingPattern13.ts +++ b/tests/cases/fourslash/completionListInObjectBindingPattern13.ts @@ -16,4 +16,5 @@ goTo.marker(); verify.completionListContains("x"); verify.completionListContains("y"); -verify.not.completionListContains("z"); \ No newline at end of file +verify.not.completionListContains("z"); +verify.not.completionListAllowsNewIdentifier(); \ No newline at end of file From 4a2e67205f5328050ae5f661ccd4740566d8a6b3 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 1 Jul 2015 13:27:50 -0700 Subject: [PATCH 07/14] Don't show a builder in object binding pattern completions. --- src/services/services.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 018a11fc4bb..798370bd7ba 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3010,18 +3010,24 @@ namespace ts { function tryGetGlobalSymbols(): boolean { let objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); if (objectLikeContainer) { - // Object literal expression, look up possible property names from contextual type + // We're looking up possible property names from contextual/inferred/declared type. isMemberCompletion = true; - isNewIdentifierLocation = true; let typeForObject: Type; let existingMembers: Declaration[]; if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + // We are completing on contextual types, but may also include properties + // other than those within the declared type. + isNewIdentifierLocation = true; + typeForObject = typeChecker.getContextualType(objectLikeContainer); existingMembers = (objectLikeContainer).properties; } else { + // We are *only* completing on properties from the type being destructured. + isNewIdentifierLocation = false; + typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); existingMembers = (objectLikeContainer).elements; } From 422a405c016787a582d43baf12936346004ea496 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 1 Jul 2015 13:34:59 -0700 Subject: [PATCH 08/14] Documentative and conservative checking. --- src/services/services.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index 798370bd7ba..516ae1bbaed 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3024,13 +3024,16 @@ namespace ts { typeForObject = typeChecker.getContextualType(objectLikeContainer); existingMembers = (objectLikeContainer).properties; } - else { + else if (objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern) { // We are *only* completing on properties from the type being destructured. isNewIdentifierLocation = false; typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); existingMembers = (objectLikeContainer).elements; } + else { + Debug.fail("Expected object literal or binding pattern, got " + objectLikeContainer.kind); + } if (!typeForObject) { return false; From 48c48417a6d1b6ab4ef383fa30e3097514b9b680 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 6 Jul 2015 11:42:06 -0700 Subject: [PATCH 09/14] Properly parse keyword-like identifiers in JSX --- src/compiler/parser.ts | 9 +++++++-- .../tsxAttributeResolution1.errors.txt | 17 +++++++++++------ .../reference/tsxAttributeResolution1.js | 11 +++++++---- .../conformance/jsx/tsxAttributeResolution1.tsx | 6 ++++-- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f2f39b7495c..64aace445cf 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1245,6 +1245,11 @@ namespace ts { return isIdentifier(); } + function nextTokenIsIdentifierOrKeyword() { + nextToken(); + return isIdentifierOrKeyword(); + } + function isHeritageClauseExtendsOrImplementsKeyword(): boolean { if (token === SyntaxKind.ImplementsKeyword || token === SyntaxKind.ExtendsKeyword) { @@ -3163,7 +3168,7 @@ namespace ts { if (sourceFile.languageVariant !== LanguageVariant.JSX) { return parseTypeAssertion(); } - if(lookAhead(nextTokenIsIdentifier)) { + if(lookAhead(nextTokenIsIdentifierOrKeyword)) { return parseJsxElementOrSelfClosingElement(); } // Fall through @@ -3381,7 +3386,7 @@ namespace ts { function parseJsxElementName(): EntityName { scanJsxIdentifier(); - let elementName: EntityName = parseIdentifier(); + let elementName: EntityName = parseIdentifierName(); while (parseOptional(SyntaxKind.DotToken)) { scanJsxIdentifier(); let node = createNode(SyntaxKind.QualifiedName, elementName.pos); diff --git a/tests/baselines/reference/tsxAttributeResolution1.errors.txt b/tests/baselines/reference/tsxAttributeResolution1.errors.txt index 54666f46aba..7503c5f5969 100644 --- a/tests/baselines/reference/tsxAttributeResolution1.errors.txt +++ b/tests/baselines/reference/tsxAttributeResolution1.errors.txt @@ -1,17 +1,19 @@ -tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(22,8): error TS2322: Type 'string' is not assignable to type 'number'. -tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(23,8): error TS2339: Property 'y' does not exist on type 'Attribs1'. +tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(23,8): error TS2322: Type 'string' is not assignable to type 'number'. tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(24,8): error TS2339: Property 'y' does not exist on type 'Attribs1'. -tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(25,8): error TS2322: Type 'string' is not assignable to type 'number'. +tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(25,8): error TS2339: Property 'y' does not exist on type 'Attribs1'. +tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(26,8): error TS2322: Type 'string' is not assignable to type 'number'. +tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(27,8): error TS2339: Property 'var' does not exist on type 'Attribs1'. tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(29,1): error TS2324: Property 'reqd' is missing in type '{ reqd: string; }'. tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(30,8): error TS2322: Type 'number' is not assignable to type 'string'. -==== tests/cases/conformance/jsx/tsxAttributeResolution1.tsx (6 errors) ==== +==== tests/cases/conformance/jsx/tsxAttributeResolution1.tsx (7 errors) ==== declare module JSX { interface Element { } interface IntrinsicElements { test1: Attribs1; test2: { reqd: string }; + var: { var: string }; } } interface Attribs1 { @@ -40,8 +42,9 @@ tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(30,8): error TS2322: Typ ; // Error, "32" is not number ~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'number'. - // TODO attribute 'var' should be parseable - // ; // Error, no 'var' property + ; // Error, no 'var' property + ~~~ +!!! error TS2339: Property 'var' does not exist on type 'Attribs1'. ; // Error, missing reqd ~~~~~~~~~ @@ -50,4 +53,6 @@ tests/cases/conformance/jsx/tsxAttributeResolution1.tsx(30,8): error TS2322: Typ ~~~~~~~~~ !!! error TS2322: Type 'number' is not assignable to type 'string'. + // Should be OK + ; \ No newline at end of file diff --git a/tests/baselines/reference/tsxAttributeResolution1.js b/tests/baselines/reference/tsxAttributeResolution1.js index 011ac8034e2..1e1efb7ea22 100644 --- a/tests/baselines/reference/tsxAttributeResolution1.js +++ b/tests/baselines/reference/tsxAttributeResolution1.js @@ -4,6 +4,7 @@ declare module JSX { interface IntrinsicElements { test1: Attribs1; test2: { reqd: string }; + var: { var: string }; } } interface Attribs1 { @@ -24,12 +25,13 @@ interface Attribs1 { ; // Error, no property "y" ; // Error, no property "y" ; // Error, "32" is not number -// TODO attribute 'var' should be parseable -// ; // Error, no 'var' property +; // Error, no 'var' property ; // Error, missing reqd ; // Error, reqd is not string +// Should be OK +; //// [tsxAttributeResolution1.jsx] @@ -44,7 +46,8 @@ interface Attribs1 { ; // Error, no property "y" ; // Error, no property "y" ; // Error, "32" is not number -// TODO attribute 'var' should be parseable -// ; // Error, no 'var' property +; // Error, no 'var' property ; // Error, missing reqd ; // Error, reqd is not string +// Should be OK +; diff --git a/tests/cases/conformance/jsx/tsxAttributeResolution1.tsx b/tests/cases/conformance/jsx/tsxAttributeResolution1.tsx index edb203655ef..eff3f3a8f43 100644 --- a/tests/cases/conformance/jsx/tsxAttributeResolution1.tsx +++ b/tests/cases/conformance/jsx/tsxAttributeResolution1.tsx @@ -5,6 +5,7 @@ declare module JSX { interface IntrinsicElements { test1: Attribs1; test2: { reqd: string }; + var: { var: string }; } } interface Attribs1 { @@ -25,9 +26,10 @@ interface Attribs1 { ; // Error, no property "y" ; // Error, no property "y" ; // Error, "32" is not number -// TODO attribute 'var' should be parseable -// ; // Error, no 'var' property +; // Error, no 'var' property ; // Error, missing reqd ; // Error, reqd is not string +// Should be OK +; From af94f1c5d09c3c8953b7f52b03b41bbf313b69c0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 6 Jul 2015 14:04:42 -0700 Subject: [PATCH 10/14] Throttle how often we call into the host side to check for cancellation. --- src/services/services.ts | 1 - src/services/shims.ts | 26 +++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 19dd5288721..1e98e29d4cf 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1618,7 +1618,6 @@ namespace ts { export class OperationCanceledException { } export class CancellationTokenObject { - public static None: CancellationTokenObject = new CancellationTokenObject(null) constructor(private cancellationToken: CancellationToken) { diff --git a/src/services/shims.ts b/src/services/shims.ts index 2e8b3eb774d..22c0d3f6065 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -327,7 +327,8 @@ namespace ts { } public getCancellationToken(): CancellationToken { - return this.shimHost.getCancellationToken(); + var hostCancellationToken = this.shimHost.getCancellationToken(); + return new ThrottledCancellationToken(hostCancellationToken); } public getCurrentDirectory(): string { @@ -346,6 +347,29 @@ namespace ts { } } + /** A cancellation that throttles calls to the host */ + class ThrottledCancellationToken implements CancellationToken { + // Store when we last tried to cancel. Checking cancellation can be expensive (as we have + // to marshall over to the host layer). So we only bother actually checking once enough + // time has passed. + private lastCancellationCheckTime = 0; + + constructor(private hostCancellationToken: CancellationToken) { + } + + public isCancellationRequested(): boolean { + var time = Date.now(); + var duration = Math.abs(time - this.lastCancellationCheckTime); + if (duration > 10) { + // Check no more than once every 10 ms. + this.lastCancellationCheckTime = time; + return this.hostCancellationToken.isCancellationRequested(); + } + + return false; + } + } + export class CoreServicesShimHostAdapter implements ParseConfigHost { constructor(private shimHost: CoreServicesShimHost) { From 370372e043dacde018f0689c1f4b836116e4260d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 6 Jul 2015 14:25:18 -0700 Subject: [PATCH 11/14] Remove shim cancellation test. --- ...cellationWhenfindingAllRefsOnDefinition.ts | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts diff --git a/tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts b/tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts deleted file mode 100644 index 09f580bb965..00000000000 --- a/tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts +++ /dev/null @@ -1,38 +0,0 @@ -/// - -//@Filename: findAllRefsOnDefinition-import.ts -////export class Test{ -//// -//// constructor(){ -//// -//// } -//// -//// public /*1*/start(){ -//// return this; -//// } -//// -//// public stop(){ -//// return this; -//// } -////} - -//@Filename: findAllRefsOnDefinition.ts -////import Second = require("findAllRefsOnDefinition-import"); -//// -////var second = new Second.Test() -////second.start(); -////second.stop(); - -goTo.file("findAllRefsOnDefinition-import.ts"); -goTo.marker("1"); - -verify.referencesCountIs(2); - -cancellation.setCancelled(); -goTo.marker("1"); -verifyOperationIsCancelled(() => verify.referencesCountIs(0) ); - -// verify that internal state is still correct -cancellation.resetCancelled(); -goTo.marker("1"); -verify.referencesCountIs(2); From 3a26cd21f99771258f257d183bdfc389e780614c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 6 Jul 2015 15:31:22 -0700 Subject: [PATCH 12/14] Adding comments. --- src/compiler/checker.ts | 12 +++++++++++- src/compiler/program.ts | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 308fe38de71..3ef2c6ada2a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22,6 +22,17 @@ namespace ts { } export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { + // Cancellation that controls whether or not we can cancel in the middle of type checking. + // In general cancelling is *not* safe for the type checker. We might be in the middle of + // computing something, and we will leave our internals in an inconsistent state. Callers + // who set the cancellation token should catch if a cancellation exception occurs, and + // should throw away and create a new TypeChecker. + // + // Currently we only support setting the cancellation token when getting diagnostics. This + // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if + // they no longer need the information (for example, if the user started editing again). + let cancellationToken: CancellationToken; + let Symbol = objectAllocator.getSymbolConstructor(); let Type = objectAllocator.getTypeConstructor(); let Signature = objectAllocator.getSignatureConstructor(); @@ -13321,7 +13332,6 @@ namespace ts { } } - var cancellationToken: CancellationToken; function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { try { // Record the cancellation token so it can be checked later on during checkSourceElement. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index ae4180cf8be..28efaf24cca 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -313,6 +313,13 @@ namespace ts { 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. + // + // Note: we are overly agressive here. We do not actually *have* to throw away + // the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep + // the lifetimes of these two TypeCheckers the same. Also, we generally only + // cancel when the user has made a change anyways. And, in that case, we (the + // program instance) will get thrown away anyways. So trying to keep one of + // these type checkers alive doesn't serve much purpose. noDiagnosticsTypeChecker = undefined; diagnosticsProducingTypeChecker = undefined; } From 93a721cf1d54e701e5b9a0c2972465997f1d9560 Mon Sep 17 00:00:00 2001 From: Yui T Date: Tue, 7 Jul 2015 09:57:58 -0700 Subject: [PATCH 13/14] Bind classExpression and functionExpression to its name if the expression is declared with name --- src/compiler/binder.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index e30178e987b..5a39e96b119 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -892,7 +892,8 @@ namespace ts { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: checkStrictModeFunctionName(node); - return bindAnonymousDeclaration(node, SymbolFlags.Function, "__function"); + let bindingName = (node).name ? (node).name.text : "__function"; + return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); case SyntaxKind.ClassExpression: case SyntaxKind.ClassDeclaration: return bindClassLikeDeclaration(node); @@ -964,7 +965,8 @@ namespace ts { bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); } else { - bindAnonymousDeclaration(node, SymbolFlags.Class, "__class"); + let bindingName = node.name ? node.name.text : "__class"; + bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); } let symbol = node.symbol; From 92d2d1957d0e6badcad6a6af228dea1df158244a Mon Sep 17 00:00:00 2001 From: Yui T Date: Tue, 7 Jul 2015 09:58:13 -0700 Subject: [PATCH 14/14] Update tests --- .../reference/classExpressionTest2.types | 6 +++--- .../renameLocationsForClassExpression01.ts | 20 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/baselines/reference/classExpressionTest2.types b/tests/baselines/reference/classExpressionTest2.types index 5586b451d3a..2ff7d79bc5f 100644 --- a/tests/baselines/reference/classExpressionTest2.types +++ b/tests/baselines/reference/classExpressionTest2.types @@ -28,13 +28,13 @@ function M() { } var v = new m(); ->v : ->new m() : +>v : C +>new m() : C >m : typeof C return v.f(); >v.f() : { t: string; x: number; } >v.f : () => { t: T; x: number; } ->v : +>v : C >f : () => { t: T; x: number; } } diff --git a/tests/cases/fourslash/renameLocationsForClassExpression01.ts b/tests/cases/fourslash/renameLocationsForClassExpression01.ts index a1174d503ed..e7247f8e4ee 100644 --- a/tests/cases/fourslash/renameLocationsForClassExpression01.ts +++ b/tests/cases/fourslash/renameLocationsForClassExpression01.ts @@ -3,13 +3,13 @@ ////class Foo { ////} //// -////var x = class /**/Foo { +////var x = class [|Foo|] { //// doIt() { -//// return Foo; +//// return [|Foo|]; //// } //// //// static doItStatically() { -//// return Foo; +//// return [|Foo|]; //// } ////} //// @@ -23,12 +23,10 @@ // TODO (yuit): Fix up this test when class expressions are supported. // Just uncomment the below, remove the marker, and add the // appropriate ranges in the test itself. -goTo.marker(); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -////let ranges = test.ranges() -////for (let range of ranges) { -//// goTo.position(range.start); -//// -//// verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -////} \ No newline at end of file +let ranges = test.ranges() +for (let range of ranges) { + goTo.position(range.start); + + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +} \ No newline at end of file