diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 0b4418b1236c8..49f834963ee1e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -754,6 +754,13 @@ namespace FourSlash { } } + public verifyCompletionListIsGlobal(expected: boolean) { + const completions = this.getCompletionListAtCaret(); + if (completions && completions.isGlobalCompletion !== expected) { + this.raiseError(`verifyCompletionListIsGlobal failed - expected result to be ${completions.isGlobalCompletion}`); + } + } + public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { const completions = this.getCompletionListAtCaret(); if (completions) { @@ -3046,6 +3053,10 @@ namespace FourSlashInterface { this.state.verifyCompletionListIsEmpty(this.negative); } + public completionListIsGlobal(expected: boolean) { + this.state.verifyCompletionListIsGlobal(expected); + } + public completionListAllowsNewIdentifier() { this.state.verifyCompletionListAllowsNewIdentifier(this.negative); } diff --git a/src/server/client.ts b/src/server/client.ts index 5032056c2f338..688408dfb88d3 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -214,6 +214,7 @@ namespace ts.server { const response = this.processResponse(request); return { + isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries: response.body.map(entry => { diff --git a/src/services/completions.ts b/src/services/completions.ts index a43717921c47f..64ea05e4f2b35 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -14,11 +14,11 @@ namespace ts.Completions { return undefined; } - const { symbols, isMemberCompletion, isNewIdentifierLocation, location, isJsDocTagName } = completionData; + const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isJsDocTagName } = completionData; if (isJsDocTagName) { // If the current position is a jsDoc tag name, only tag names should be provided for completion - return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: JsDoc.getAllJsDocCompletionEntries() }; + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries: JsDoc.getAllJsDocCompletionEntries() }; } const entries: CompletionEntry[] = []; @@ -56,7 +56,7 @@ namespace ts.Completions { addRange(entries, keywordCompletions); } - return { isMemberCompletion, isNewIdentifierLocation: isNewIdentifierLocation, entries }; + return { isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation: isNewIdentifierLocation, entries }; function getJavaScriptCompletionEntries(sourceFile: SourceFile, position: number, uniqueNames: Map): CompletionEntry[] { const entries: CompletionEntry[] = []; @@ -190,7 +190,7 @@ namespace ts.Completions { if (type) { getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, element, /*performCharacterChecks*/false); if (entries.length) { - return { isMemberCompletion: true, isNewIdentifierLocation: true, entries }; + return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries }; } } } @@ -209,7 +209,7 @@ namespace ts.Completions { } if (entries.length) { - return { isMemberCompletion: false, isNewIdentifierLocation: true, entries }; + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: true, entries }; } return undefined; @@ -221,7 +221,7 @@ namespace ts.Completions { if (type) { getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false); if (entries.length) { - return { isMemberCompletion: true, isNewIdentifierLocation: true, entries }; + return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries }; } } return undefined; @@ -233,7 +233,7 @@ namespace ts.Completions { const entries: CompletionEntry[] = []; addStringLiteralCompletionsFromType(type, entries); if (entries.length) { - return { isMemberCompletion: false, isNewIdentifierLocation: false, entries }; + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; } } return undefined; @@ -281,6 +281,7 @@ namespace ts.Completions { entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span); } return { + isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: true, entries @@ -558,6 +559,7 @@ namespace ts.Completions { } return { + isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: true, entries @@ -812,7 +814,7 @@ namespace ts.Completions { } if (isJsDocTagName) { - return { symbols: undefined, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, isJsDocTagName }; + return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, isJsDocTagName }; } if (!insideJsDocTagExpression) { @@ -884,6 +886,7 @@ namespace ts.Completions { } const semanticStart = timestamp(); + let isGlobalCompletion = false; let isMemberCompletion: boolean; let isNewIdentifierLocation: boolean; let symbols: Symbol[] = []; @@ -919,14 +922,16 @@ namespace ts.Completions { if (!tryGetGlobalSymbols()) { return undefined; } + isGlobalCompletion = true; } log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - return { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), isJsDocTagName }; + return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), isJsDocTagName }; function getTypeScriptMemberSymbols(): void { // Right of dot member completion list + isGlobalCompletion = false; isMemberCompletion = true; isNewIdentifierLocation = false; diff --git a/src/services/types.ts b/src/services/types.ts index 874d96fccc312..f84986bb534e7 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -503,6 +503,7 @@ namespace ts { } export interface CompletionInfo { + isGlobalCompletion: boolean; isMemberCompletion: boolean; isNewIdentifierLocation: boolean; // true when the current location also allows for a new identifier entries: CompletionEntry[]; diff --git a/tests/cases/fourslash/completionListIsGlobalCompletion.ts b/tests/cases/fourslash/completionListIsGlobalCompletion.ts new file mode 100644 index 0000000000000..3a7adb465a10e --- /dev/null +++ b/tests/cases/fourslash/completionListIsGlobalCompletion.ts @@ -0,0 +1,42 @@ +/// + +/////// // no globals in reference paths +////import { /*2*/ } from "./file.ts"; // no globals in imports +////var test = "/*3*/"; // no globals in strings +/////*4*/class A { // insert globals +//// foo(): string { return ''; } +////} +//// +////class /*5*/B extends A { // no globals after class keyword +//// bar(): string { +//// /*6*/ // insert globals +//// return ''; +//// } +////} +//// +////class C { // no globals at beginning of generics +//// x: U; +//// y = this./*8*/x; // no globals inserted for member completions +//// /*9*/ // insert globals +////} +/////*10*/ // insert globals +goTo.marker("1"); +verify.completionListIsGlobal(false); +goTo.marker("2"); +verify.completionListIsGlobal(false); +goTo.marker("3"); +verify.completionListIsGlobal(false); +goTo.marker("4"); +verify.completionListIsGlobal(true); +goTo.marker("5"); +verify.completionListIsGlobal(false); +goTo.marker("6"); +verify.completionListIsGlobal(true); +goTo.marker("7"); +verify.completionListIsGlobal(false); +goTo.marker("8"); +verify.completionListIsGlobal(false); +goTo.marker("9"); +verify.completionListIsGlobal(true); +goTo.marker("10"); +verify.completionListIsGlobal(true);