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; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index df6796947fc..773f1e3f7d0 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(); @@ -194,10 +205,10 @@ namespace ts { return checker; - function getEmitResolver(sourceFile?: SourceFile) { + 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); + getDiagnostics(sourceFile, cancellationToken); return emitResolver; } @@ -13028,8 +13039,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: @@ -13305,7 +13332,20 @@ namespace ts { } } - function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] { + 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 + // this call is done. + 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 823349eda91..427084b5779 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -805,4 +805,4 @@ namespace ts { Debug.assert(false, message); } } -} +} diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 2d3746212b6..3635a88b608 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1246,6 +1246,11 @@ namespace ts { return isIdentifier(); } + function nextTokenIsIdentifierOrKeyword() { + nextToken(); + return isIdentifierOrKeyword(); + } + function isHeritageClauseExtendsOrImplementsKeyword(): boolean { if (token === SyntaxKind.ImplementsKeyword || token === SyntaxKind.ExtendsKeyword) { @@ -3172,7 +3177,7 @@ namespace ts { if (sourceFile.languageVariant !== LanguageVariant.JSX) { return parseTypeAssertion(); } - if(lookAhead(nextTokenIsIdentifier)) { + if(lookAhead(nextTokenIsIdentifierOrKeyword)) { return parseJsxElementOrSelfClosingElement(); } // Fall through @@ -3390,7 +3395,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/src/compiler/program.ts b/src/compiler/program.ts index b9321ab3461..28efaf24cca 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -104,14 +104,14 @@ namespace ts { }; } - export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile): Diagnostic[] { - let diagnostics = program.getOptionsDiagnostics().concat( - program.getSyntacticDiagnostics(sourceFile), - program.getGlobalDiagnostics(), - program.getSemanticDiagnostics(sourceFile)); + export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] { + let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat( + program.getSyntacticDiagnostics(sourceFile, cancellationToken), + program.getGlobalDiagnostics(cancellationToken), + program.getSemanticDiagnostics(sourceFile, cancellationToken)); if (program.getCompilerOptions().declaration) { - diagnostics.concat(program.getDeclarationDiagnostics(sourceFile)); + diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken)); } return sortAndDeduplicateDiagnostics(diagnostics); @@ -233,10 +233,15 @@ namespace ts { return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback): 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: CancellationToken): 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 }; } @@ -265,53 +270,86 @@ namespace ts { return filesByName.get(fileName); } - function getDiagnosticsHelper(sourceFile: SourceFile, getDiagnostics: (sourceFile: SourceFile) => Diagnostic[]): Diagnostic[] { + function getDiagnosticsHelper( + sourceFile: SourceFile, + getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[], + cancellationToken: CancellationToken): 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: CancellationToken): Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); } - function getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[] { - return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile); + function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); } - function getDeclarationDiagnostics(sourceFile?: SourceFile): Diagnostic[] { - return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile); + function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); } - function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] { + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): 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. + // + // 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; + } - 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: CancellationToken): 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: CancellationToken): 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 getOptionsDiagnostics(): Diagnostic[] { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4117ad09f75..6c2f72a1ab7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1290,6 +1290,15 @@ namespace ts { (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void): void; } + export class OperationCanceledException { } + + export interface CancellationToken { + isCancellationRequested(): boolean; + + /** @throws OperationCanceledException if isCancellationRequested is true */ + throwIfCancellationRequested(): void; + } + export interface Program extends ScriptReferenceHost { /** * Get a list of files in the program @@ -1306,13 +1315,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?: CancellationToken): EmitResult; - getOptionsDiagnostics(): Diagnostic[]; - getGlobalDiagnostics(): Diagnostic[]; - getSyntacticDiagnostics(sourceFile?: SourceFile): Diagnostic[]; - getSemanticDiagnostics(sourceFile?: SourceFile): Diagnostic[]; - getDeclarationDiagnostics(sourceFile?: SourceFile): 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. @@ -1423,9 +1432,9 @@ namespace ts { getJsxIntrinsicTagNames(): Symbol[]; // Should not be called directly. Should only be accessed through the Program instance. - /* @internal */ getDiagnostics(sourceFile?: SourceFile): Diagnostic[]; + /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /* @internal */ getGlobalDiagnostics(): Diagnostic[]; - /* @internal */ getEmitResolver(sourceFile?: SourceFile): EmitResolver; + /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken): EmitResolver; /* @internal */ getNodeCount(): number; /* @internal */ getIdentifierCount(): number; @@ -2178,14 +2187,9 @@ namespace ts { verticalTab = 0x0B, // \v } - export interface CancellationToken { - isCancellationRequested(): boolean; - } - 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 475a73ccd7c..c374f8add60 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 af11b5d3b05..8260d41c837 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; @@ -1618,26 +1622,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. @@ -2404,6 +2388,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; @@ -2608,7 +2607,7 @@ namespace ts { function getSyntacticDiagnostics(fileName: string) { synchronizeHostData(); - return program.getSyntacticDiagnostics(getValidSourceFile(fileName)); + return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken); } /** @@ -2630,13 +2629,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); } @@ -2799,7 +2798,8 @@ namespace ts { function getCompilerOptionsDiagnostics() { synchronizeHostData(); - return program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + return program.getOptionsDiagnostics(cancellationToken).concat( + program.getGlobalDiagnostics(cancellationToken)); } /** @@ -3029,21 +3029,30 @@ namespace ts { let objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); let jsxContainer = tryGetContainingJsxElement(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 { + 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; @@ -5842,7 +5851,7 @@ namespace ts { }); } - let emitOutput = program.emit(sourceFile, writeFile); + let emitOutput = program.emit(sourceFile, writeFile, cancellationToken); return { outputFiles, @@ -6090,6 +6099,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(); @@ -6157,7 +6186,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 @@ -6524,6 +6556,8 @@ namespace ts { // Ignore nodes that don't intersect the original span to classify. if (decodedTextSpanIntersectsWith(spanStart, spanLength, element.pos, element.getFullWidth())) { + checkForClassificationCancellation(element.kind); + let children = element.getChildren(sourceFile); for (let i = 0, n = children.length; i < n; i++) { let child = children[i]; diff --git a/src/services/shims.ts b/src/services/shims.ts index 2e8b3eb774d..6e765eff499 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,8 +326,9 @@ namespace ts { } } - public getCancellationToken(): CancellationToken { - return this.shimHost.getCancellationToken(); + public getCancellationToken(): HostCancellationToken { + 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 HostCancellationToken { + // 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: HostCancellationToken) { + } + + 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) { 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 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/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 +; 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 diff --git a/tests/cases/fourslash/renameLocationsForClassExpression01.ts b/tests/cases/fourslash/renameLocationsForClassExpression01.ts index 6bedc077d4d..b95040085c9 100644 --- a/tests/cases/fourslash/renameLocationsForClassExpression01.ts +++ b/tests/cases/fourslash/renameLocationsForClassExpression01.ts @@ -3,7 +3,6 @@ ////class Foo { ////} //// -//////The class expression Foo ////var x = class [|Foo|] { //// doIt() { //// return [|Foo|]; 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/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); 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("}"));