diff --git a/src/services/services.ts b/src/services/services.ts index 19e0873d8b5..28c0b0ce239 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3010,68 +3010,21 @@ namespace ts { } function tryGetGlobalSymbols(): boolean { - let objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); - let jsxContainer = tryGetContainingJsxElement(contextToken); - if (objectLikeContainer) { - // We're looking up possible property names from contextual/inferred/declared type. - isMemberCompletion = true; + let objectLikeContainer: ObjectLiteralExpression | BindingPattern; + let importClause: ImportClause; + let jsxContainer: JsxOpeningLikeElement; - 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 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; - } - - let typeMembers = typeChecker.getPropertiesOfType(typeForObject); - if (typeMembers && typeMembers.length > 0) { - // Add filtered items to the completion list - symbols = filterObjectMembersList(typeMembers, existingMembers); - } - return true; + if (objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken)) { + return tryGetObjectLikeCompletionSymbols(objectLikeContainer); } - else if (getAncestor(contextToken, SyntaxKind.ImportClause)) { - // cursor is in import clause + + if (importClause = getAncestor(contextToken, SyntaxKind.ImportClause)) { + // cursor is in an import clause // try to show exported member for imported module - isMemberCompletion = true; - isNewIdentifierLocation = true; - if (showCompletionsInImportsClause(contextToken)) { - let importDeclaration = getAncestor(contextToken, SyntaxKind.ImportDeclaration); - Debug.assert(importDeclaration !== undefined); - - let exports: Symbol[]; - if (importDeclaration.moduleSpecifier) { - let moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(importDeclaration.moduleSpecifier); - if (moduleSpecifierSymbol) { - exports = typeChecker.getExportsOfModule(moduleSpecifierSymbol); - } - } - - //let exports = typeInfoResolver.getExportsOfImportDeclaration(importDeclaration); - symbols = exports ? filterModuleExports(exports, importDeclaration) : emptyArray; - } - return true; + return tryGetImportClauseCompletionSymbols(importClause); } - else if (jsxContainer) { + + if (jsxContainer = tryGetContainingJsxElement(contextToken)) { let attrsType: Type; if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) { // Cursor is inside a JSX self-closing element or opening element @@ -3153,7 +3106,7 @@ namespace ts { return result; } - function showCompletionsInImportsClause(node: Node): boolean { + function shouldShowCompletionsInImportsClause(node: Node): boolean { if (node) { // import {| // import {a,| @@ -3251,6 +3204,86 @@ namespace ts { return false; } + /** + * Aggregates relevant symbols for completion in object literals and object binding patterns. + * Relevant symbols are stored in the captured 'symbols' variable. + * + * @returns true if 'symbols' was successfully populated; false otherwise. + */ + function tryGetObjectLikeCompletionSymbols(objectLikeContainer: ObjectLiteralExpression | BindingPattern): boolean { + // We're looking up possible property names from contextual/inferred/declared type. + isMemberCompletion = 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 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; + } + + let typeMembers = typeChecker.getPropertiesOfType(typeForObject); + if (typeMembers && typeMembers.length > 0) { + // Add filtered items to the completion list + symbols = filterObjectMembersList(typeMembers, existingMembers); + } + return true; + } + + /** + * Aggregates relevant symbols for completion in import clauses; for instance, + * + * import { $ } from "moduleName"; + * + * Relevant symbols are stored in the captured 'symbols' variable. + * + * @returns true if 'symbols' was successfully populated; false otherwise. + */ + function tryGetImportClauseCompletionSymbols(importClause: ImportClause): boolean { + // cursor is in import clause + // try to show exported member for imported module + if (shouldShowCompletionsInImportsClause(contextToken)) { + isMemberCompletion = true; + isNewIdentifierLocation = false; + + let importDeclaration = importClause.parent; + Debug.assert(importDeclaration !== undefined && importDeclaration.kind === SyntaxKind.ImportDeclaration); + + let exports: Symbol[]; + let moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(importDeclaration.moduleSpecifier); + if (moduleSpecifierSymbol) { + exports = typeChecker.getExportsOfModule(moduleSpecifierSymbol); + } + + //let exports = typeInfoResolver.getExportsOfImportDeclaration(importDeclaration); + symbols = exports ? filterModuleExports(exports, importDeclaration) : emptyArray; + } + else { + isMemberCompletion = false; + isNewIdentifierLocation = true; + } + + return true; + } + /** * Returns the immediate owning object literal or binding pattern of a context token, * on the condition that one exists and that the context implies completion should be given. diff --git a/tests/cases/fourslash/completionForExports.ts b/tests/cases/fourslash/completionForExports.ts deleted file mode 100644 index 57c0f75efc1..00000000000 --- a/tests/cases/fourslash/completionForExports.ts +++ /dev/null @@ -1,32 +0,0 @@ -/// - -// @Filename: m1.ts -////export var foo: number = 1; -////export function bar() { return 10; } -////export function baz() { return 10; } - -// @Filename: m2.ts -////import {/*1*/, /*2*/ from "m1" -////import {/*3*/} from "m1" -////import {foo,/*4*/ from "m1" -////import {bar as /*5*/, /*6*/ from "m1" -////import {foo, bar, baz as b,/*7*/} from "m1" -function verifyCompletionAtMarker(marker: string, ...completions: string[]) { - goTo.marker(marker); - if (completions.length) { - for (var i = 0; i < completions.length; ++i) { - verify.completionListContains(completions[i]); - } - } - else { - verify.completionListIsEmpty(); - } -} - -verifyCompletionAtMarker("1", "foo", "bar", "baz"); -verifyCompletionAtMarker("2", "foo", "bar", "baz"); -verifyCompletionAtMarker("3", "foo", "bar", "baz"); -verifyCompletionAtMarker("4", "bar", "baz"); -verifyCompletionAtMarker("5"); -verifyCompletionAtMarker("6", "foo", "baz"); -verifyCompletionAtMarker("7"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInImportClause01.ts b/tests/cases/fourslash/completionListInImportClause01.ts new file mode 100644 index 00000000000..1191216baf1 --- /dev/null +++ b/tests/cases/fourslash/completionListInImportClause01.ts @@ -0,0 +1,39 @@ +/// + +// @Filename: m1.ts +////export var foo: number = 1; +////export function bar() { return 10; } +////export function baz() { return 10; } + +// @Filename: m2.ts +////import {/*1*/, /*2*/ from "m1" +////import {/*3*/} from "m1" +////import {foo,/*4*/ from "m1" +////import {bar as /*5*/, /*6*/ from "m1" +////import {foo, bar, baz as b,/*7*/} from "m1" +function verifyCompletionAtMarker(marker: string, showBuilder: boolean, ...completions: string[]) { + goTo.marker(marker); + if (completions.length) { + for (let completion of completions) { + verify.completionListContains(completion); + } + } + else { + verify.completionListIsEmpty(); + } + + if (showBuilder) { + verify.completionListAllowsNewIdentifier(); + } + else { + verify.not.completionListAllowsNewIdentifier(); + } +} + +verifyCompletionAtMarker("1", /*showBuilder*/ false, "foo", "bar", "baz"); +verifyCompletionAtMarker("2", /*showBuilder*/ false, "foo", "bar", "baz"); +verifyCompletionAtMarker("3", /*showBuilder*/ false, "foo", "bar", "baz"); +verifyCompletionAtMarker("4", /*showBuilder*/ false, "bar", "baz"); +verifyCompletionAtMarker("5", /*showBuilder*/ true); +verifyCompletionAtMarker("6", /*showBuilder*/ false, "foo", "baz"); +verifyCompletionAtMarker("7", /*showBuilder*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInImportClause02.ts b/tests/cases/fourslash/completionListInImportClause02.ts new file mode 100644 index 00000000000..9cf216f4578 --- /dev/null +++ b/tests/cases/fourslash/completionListInImportClause02.ts @@ -0,0 +1,13 @@ +/// + +////declare module "M1" { +//// export var V; +////} +//// +////declare module "M2" { +//// import { /**/ } from "M1" +////} + +goTo.marker(); +verify.completionListContains("V"); +verify.not.completionListAllowsNewIdentifier();