diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9e4e81e3da9..a916e9cbba6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -108,6 +108,7 @@ namespace ts { getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, + getExportsAndPropertiesOfModule, getAmbientModules, getJsxElementAttributesType, getJsxIntrinsicTagNames, @@ -1527,6 +1528,15 @@ namespace ts { return symbolsToArray(getExportsOfModule(moduleSymbol)); } + function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] { + const exports = getExportsOfModuleAsArray(moduleSymbol); + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + addRange(exports, getPropertiesOfType(getTypeOfSymbol(exportEquals))); + } + return exports; + } + function tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined { const symbolTable = getExportsOfModule(moduleSymbol); if (symbolTable) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 954ad21ba29..0906488ea33 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2370,6 +2370,8 @@ namespace ts { isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; getExportsOfModule(moduleSymbol: Symbol): Symbol[]; + /** Unlike `getExportsOfModule`, this includes properties of an `export =` value. */ + /* @internal */ getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[]; getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type; getJsxIntrinsicTagNames(): Symbol[]; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c41c9af9f4e..f0f2f87175e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -425,8 +425,7 @@ namespace FourSlash { } private raiseError(message: string) { - message = this.messageAtLastKnownMarker(message); - throw new Error(message); + throw new Error(this.messageAtLastKnownMarker(message)); } private messageAtLastKnownMarker(message: string) { @@ -723,6 +722,27 @@ namespace FourSlash { } } + public verifyCompletionsAt(markerName: string, expected: string[]) { + this.goToMarker(markerName); + + const actualCompletions = this.getCompletionListAtCaret(); + if (!actualCompletions) { + this.raiseError(`No completions at position '${this.currentCaretPosition}'.`); + } + + const actual = actualCompletions.entries; + + if (actual.length !== expected.length) { + this.raiseError(`Expected ${expected.length} completions, got ${actual.map(a => a.name)}.`); + } + + ts.zipWith(actual, expected, (completion, expectedCompletion, index) => { + if (completion.name !== expectedCompletion) { + this.raiseError(`Expected completion at index ${index} to be ${expectedCompletion}, got ${completion.name}`); + } + }); + } + public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { const completions = this.getCompletionListAtCaret(); if (completions) { @@ -3166,6 +3186,10 @@ namespace FourSlashInterface { super(state); } + public completionsAt(markerName: string, completions: string[]) { + this.state.verifyCompletionsAt(markerName, completions); + } + public quickInfoIs(expectedText: string, expectedDocumentation?: string) { this.state.verifyQuickInfoString(expectedText, expectedDocumentation); } diff --git a/src/services/completions.ts b/src/services/completions.ts index d893e7212ec..bb5922af9ad 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1318,14 +1318,14 @@ namespace ts.Completions { isMemberCompletion = true; isNewIdentifierLocation = false; - let exports: Symbol[]; - const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(importOrExportDeclaration.moduleSpecifier); - if (moduleSpecifierSymbol) { - exports = typeChecker.getExportsOfModule(moduleSpecifierSymbol); + const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); + if (!moduleSpecifierSymbol) { + symbols = emptyArray; + return true; } - symbols = exports ? filterNamedImportOrExportCompletionItems(exports, namedImportsOrExports.elements) : emptyArray; - + const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); + symbols = filterNamedImportOrExportCompletionItems(exports, namedImportsOrExports.elements); return true; } diff --git a/tests/cases/fourslash/completionListForExportEquals.ts b/tests/cases/fourslash/completionListForExportEquals.ts new file mode 100644 index 00000000000..28b20177c57 --- /dev/null +++ b/tests/cases/fourslash/completionListForExportEquals.ts @@ -0,0 +1,16 @@ + +/// + +// @Filename: /node_modules/foo/index.d.ts +////export = Foo; +////declare var Foo: Foo.Static; +////declare namespace Foo { +//// interface Static { +//// foo(): void; +//// } +////} + +// @Filename: /a.ts +////import { /**/ } from "foo"; + +verify.completionsAt("", ["Static", "foo"]); diff --git a/tests/cases/fourslash/completionListForExportEquals2.ts b/tests/cases/fourslash/completionListForExportEquals2.ts new file mode 100644 index 00000000000..a7b0772d55e --- /dev/null +++ b/tests/cases/fourslash/completionListForExportEquals2.ts @@ -0,0 +1,14 @@ + +/// + +// @Filename: /node_modules/foo/index.d.ts +////export = Foo; +////interface Foo { bar: number; } +////declare namespace Foo { +//// interface Static {} +////} + +// @Filename: /a.ts +////import { /**/ } from "foo"; + +verify.completionsAt("", ["Static"]); diff --git a/tests/cases/fourslash/completionListInImportClause04.ts b/tests/cases/fourslash/completionListInImportClause04.ts index 672c754e807..fa49b57c8d7 100644 --- a/tests/cases/fourslash/completionListInImportClause04.ts +++ b/tests/cases/fourslash/completionListInImportClause04.ts @@ -11,10 +11,7 @@ // @Filename: app.ts ////import {/*1*/} from './foo'; -goTo.marker('1'); -verify.completionListContains('prop1'); -verify.completionListContains('prop2'); -verify.not.completionListContains('Foo'); +verify.completionsAt("1", ["prototype", "prop1", "prop2"]); verify.numberOfErrorsInCurrentFile(0); goTo.marker('2'); verify.numberOfErrorsInCurrentFile(0); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 4397839fbe8..0de303238f1 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -138,6 +138,7 @@ declare namespace FourSlashInterface { class verify extends verifyNegatable { assertHasRanges(ranges: Range[]): void; caretAtMarker(markerName?: string): void; + completionsAt(markerName: string, completions: string[]): void; indentationIs(numberOfSpaces: number): void; indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle?: ts.IndentStyle, baseIndentSize?: number): void; textAtCaretIs(text: string): void;