From 890676a5d8f9f5c76057ab4556bf7cc7e8ebf2df Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 11 Jan 2017 14:28:22 -0800 Subject: [PATCH 1/2] Include properties of an `export =` value in import completions. --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 1 + src/harness/fourslash.ts | 28 +++++++++++++++++-- src/services/completions.ts | 6 ++++ .../completionListForExportEquals.ts | 16 +++++++++++ .../completionListForExportEquals2.ts | 14 ++++++++++ .../completionListInImportClause04.ts | 5 +--- tests/cases/fourslash/fourslash.ts | 1 + 8 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 tests/cases/fourslash/completionListForExportEquals.ts create mode 100644 tests/cases/fourslash/completionListForExportEquals2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cc4afc8aa3e..46a381a561e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -108,6 +108,7 @@ namespace ts { getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, + resolveExternalModuleSymbol, getAmbientModules, getJsxElementAttributesType, getJsxIntrinsicTagNames, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 954ad21ba29..8b3b821a25b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2370,6 +2370,7 @@ namespace ts { isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; getExportsOfModule(moduleSymbol: Symbol): Symbol[]; + /* @internal */ resolveExternalModuleSymbol(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..27cd78bbe17 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1322,6 +1322,12 @@ namespace ts.Completions { const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(importOrExportDeclaration.moduleSpecifier); if (moduleSpecifierSymbol) { exports = typeChecker.getExportsOfModule(moduleSpecifierSymbol); + const exportEquals = typeChecker.resolveExternalModuleSymbol(moduleSpecifierSymbol); + if (exportEquals !== moduleSpecifierSymbol) { + // Location doesn't matter so long as it's not an identifier. + const exportEqualsType = typeChecker.getTypeOfSymbolAtLocation(exportEquals, moduleSpecifier); + exports = ts.concatenate(exports, typeChecker.getPropertiesOfType(exportEqualsType)); + } } symbols = exports ? filterNamedImportOrExportCompletionItems(exports, namedImportsOrExports.elements) : emptyArray; 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; From 765114fccd08440a2676da74ee76229f571d8330 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 13 Jan 2017 07:57:01 -0800 Subject: [PATCH 2/2] Refactor to move code into checker --- src/compiler/checker.ts | 11 ++++++++++- src/compiler/types.ts | 3 ++- src/services/completions.ts | 18 ++++++------------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 46a381a561e..a5d1e439879 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -108,7 +108,7 @@ namespace ts { getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, - resolveExternalModuleSymbol, + getExportsAndPropertiesOfModule, getAmbientModules, getJsxElementAttributesType, getJsxIntrinsicTagNames, @@ -1528,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 8b3b821a25b..0906488ea33 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2370,7 +2370,8 @@ namespace ts { isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; getAliasedSymbol(symbol: Symbol): Symbol; getExportsOfModule(moduleSymbol: Symbol): Symbol[]; - /* @internal */ resolveExternalModuleSymbol(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/services/completions.ts b/src/services/completions.ts index 27cd78bbe17..bb5922af9ad 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1318,20 +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 exportEquals = typeChecker.resolveExternalModuleSymbol(moduleSpecifierSymbol); - if (exportEquals !== moduleSpecifierSymbol) { - // Location doesn't matter so long as it's not an identifier. - const exportEqualsType = typeChecker.getTypeOfSymbolAtLocation(exportEquals, moduleSpecifier); - exports = ts.concatenate(exports, typeChecker.getPropertiesOfType(exportEqualsType)); - } + 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; }