diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a18f2921a9a53..00426d2aaf38c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11993,7 +11993,7 @@ namespace ts { return resolveExternalModuleName(node, node); } - // Intentional fall-through + // fall through case SyntaxKind.NumericLiteral: // index access if (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent).argumentExpression === node) { @@ -12060,6 +12060,10 @@ namespace ts { return symbol && getTypeOfSymbol(symbol); } + if (isBindingPattern(node)) { + return getTypeForVariableLikeDeclaration(node.parent); + } + if (isInRightSideOfImportOrExportAssignment(node)) { let symbol = getSymbolInfo(node); let declaredType = symbol && getDeclaredTypeOfSymbol(symbol); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 5915698462ca3..ff5baddbb7085 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -660,8 +660,7 @@ module FourSlash { } errorMsg += "]\n"; - Harness.IO.log(errorMsg); - this.raiseError("Member list is not empty at Caret"); + this.raiseError("Member list is not empty at Caret: " + errorMsg); } } diff --git a/src/services/services.ts b/src/services/services.ts index 18510b9a178b8..f4e37bf56eaa4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2985,21 +2985,32 @@ namespace ts { } function tryGetGlobalSymbols(): boolean { - let containingObjectLiteral = getContainingObjectLiteralApplicableForCompletion(contextToken); - if (containingObjectLiteral) { + let objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); + if (objectLikeContainer) { // Object literal expression, look up possible property names from contextual type isMemberCompletion = true; isNewIdentifierLocation = true; - let contextualType = typeChecker.getContextualType(containingObjectLiteral); - if (!contextualType) { + let typeForObject: Type; + let existingMembers: Declaration[]; + + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + typeForObject = typeChecker.getContextualType(objectLikeContainer); + existingMembers = (objectLikeContainer).properties; + } + else { + typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); + existingMembers = (objectLikeContainer).elements; + } + + if (!typeForObject) { return false; } - let contextualTypeMembers = typeChecker.getPropertiesOfType(contextualType); - if (contextualTypeMembers && contextualTypeMembers.length > 0) { + let typeMembers = typeChecker.getPropertiesOfType(typeForObject); + if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list - symbols = filterContextualMembersList(contextualTypeMembers, containingObjectLiteral.properties); + symbols = filterObjectMembersList(typeMembers, existingMembers); } } else if (getAncestor(contextToken, SyntaxKind.ImportClause)) { @@ -3187,17 +3198,18 @@ namespace ts { return false; } - function getContainingObjectLiteralApplicableForCompletion(previousToken: Node): ObjectLiteralExpression { - // The locations in an object literal expression that are applicable for completion are property name definition locations. - - if (previousToken) { - let parent = previousToken.parent; - - switch (previousToken.kind) { + /** + * 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. + */ + function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | BindingPattern { + if (contextToken) { + switch (contextToken.kind) { case SyntaxKind.OpenBraceToken: // let x = { | case SyntaxKind.CommaToken: // let x = { a: 0, | - if (parent && parent.kind === SyntaxKind.ObjectLiteralExpression) { - return parent; + let parent = contextToken.parent; + if (parent && (parent.kind === SyntaxKind.ObjectLiteralExpression || parent.kind === SyntaxKind.ObjectBindingPattern)) { + return parent; } break; } @@ -3236,8 +3248,7 @@ namespace ts { containingNodeKind === SyntaxKind.ClassDeclaration || // class A !lookUp(exisingImports, e.name)); } - function filterContextualMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] { + function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] { if (!existingMembers || existingMembers.length === 0) { return contextualMemberSymbols; } let existingMemberNames: Map = {}; - forEach(existingMembers, m => { - if (m.kind !== SyntaxKind.PropertyAssignment && m.kind !== SyntaxKind.ShorthandPropertyAssignment) { - // Ignore omitted expressions for missing members in the object literal - return; + for (let m of existingMembers) { + // Ignore omitted expressions for missing members + if (m.kind !== SyntaxKind.PropertyAssignment && + m.kind !== SyntaxKind.ShorthandPropertyAssignment && + m.kind !== SyntaxKind.BindingElement) { + continue; } + // If this is the current item we are editing right now, do not filter it out if (m.getStart() <= position && position <= m.getEnd()) { - // If this is the current item we are editing right now, do not filter it out - return; + continue; } - // TODO(jfreeman): Account for computed property name - existingMemberNames[(m.name).text] = true; - }); + let existingName: string; + + if (m.kind === SyntaxKind.BindingElement && (m).propertyName) { + existingName = (m).propertyName.text; + } + else { + // TODO(jfreeman): Account for computed property name + // NOTE: if one only performs this step when m.name is an identifier, + // things like '__proto__' are not filtered out. + existingName = (m.name).text; + } + + existingMemberNames[existingName] = true; + } let filteredMembers: Symbol[] = []; forEach(contextualMemberSymbols, s => { diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern01.ts b/tests/cases/fourslash/completionListInObjectBindingPattern01.ts new file mode 100644 index 0000000000000..128e95b8ed3c7 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern01.ts @@ -0,0 +1,13 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////var foo: I; +////var { /**/ } = foo; + +goTo.marker(); +verify.completionListContains("property1"); +verify.completionListContains("property2"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern02.ts b/tests/cases/fourslash/completionListInObjectBindingPattern02.ts new file mode 100644 index 0000000000000..6d2b7a6b5164d --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern02.ts @@ -0,0 +1,13 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////var foo: I; +////var { property1, /**/ } = foo; + +goTo.marker(); +verify.completionListContains("property2"); +verify.not.completionListContains("property1"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern03.ts b/tests/cases/fourslash/completionListInObjectBindingPattern03.ts new file mode 100644 index 0000000000000..72da68168bde9 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern03.ts @@ -0,0 +1,13 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////var foo: I; +////var { property1: /**/ } = foo; + +goTo.marker(); +verify.completionListAllowsNewIdentifier(); +verify.completionListIsEmpty(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern04.ts b/tests/cases/fourslash/completionListInObjectBindingPattern04.ts new file mode 100644 index 0000000000000..92b202a1b37fb --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern04.ts @@ -0,0 +1,13 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////var foo: I; +////var { prope/**/ } = foo; + +goTo.marker(); +verify.completionListContains("property1"); +verify.completionListContains("property2"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern05.ts b/tests/cases/fourslash/completionListInObjectBindingPattern05.ts new file mode 100644 index 0000000000000..d17e11810dfab --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern05.ts @@ -0,0 +1,12 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////var foo: I; +////var { property1/**/ } = foo; + +goTo.marker(); +verify.completionListContains("property1"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern06.ts b/tests/cases/fourslash/completionListInObjectBindingPattern06.ts new file mode 100644 index 0000000000000..8060c2bde78a2 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern06.ts @@ -0,0 +1,12 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////var foo: I; +////var { property1, property2, /**/ } = foo; + +goTo.marker(); +verify.completionListIsEmpty(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern07.ts b/tests/cases/fourslash/completionListInObjectBindingPattern07.ts new file mode 100644 index 0000000000000..a3015346aa386 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern07.ts @@ -0,0 +1,18 @@ +/// + +////interface I { +//// propertyOfI_1: number; +//// propertyOfI_2: string; +////} +////interface J { +//// property1: I; +//// property2: string; +////} +//// +////var foo: J; +////var { property1: { /**/ } } = foo; + +goTo.marker(); +verify.completionListContains("propertyOfI_1"); +verify.completionListContains("propertyOfI_2"); +verify.not.completionListContains("property2"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern08.ts b/tests/cases/fourslash/completionListInObjectBindingPattern08.ts new file mode 100644 index 0000000000000..6d6be330b8328 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern08.ts @@ -0,0 +1,18 @@ +/// + +////interface I { +//// propertyOfI_1: number; +//// propertyOfI_2: string; +////} +////interface J { +//// property1: I; +//// property2: string; +////} +//// +////var foo: J; +////var { property1: { propertyOfI_1, /**/ } } = foo; + +goTo.marker(); +verify.completionListContains("propertyOfI_2"); +verify.not.completionListContains("propertyOfI_1"); +verify.not.completionListContains("property2"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern09.ts b/tests/cases/fourslash/completionListInObjectBindingPattern09.ts new file mode 100644 index 0000000000000..2698e78667be4 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern09.ts @@ -0,0 +1,19 @@ +/// + +////interface I { +//// propertyOfI_1: number; +//// propertyOfI_2: string; +////} +////interface J { +//// property1: I; +//// property2: string; +////} +//// +////var foo: J; +////var { property1: { propertyOfI_1, }, /**/ } = foo; + +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 diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern10.ts b/tests/cases/fourslash/completionListInObjectBindingPattern10.ts new file mode 100644 index 0000000000000..fcc09d1ead446 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern10.ts @@ -0,0 +1,23 @@ +/// + +////interface I { +//// propertyOfI_1: number; +//// propertyOfI_2: string; +////} +////interface J { +//// property1: I; +//// property2: string; +////} +//// +////var foo: J[]; +////var [{ property1: { propertyOfI_1, }, /*1*/ }, { /*2*/ }] = foo; + +goTo.marker("1"); +verify.completionListContains("property2"); +verify.not.completionListContains("property1"); +verify.not.completionListContains("propertyOfI_2"); +verify.not.completionListContains("propertyOfI_1"); + +goTo.marker("2"); +verify.completionListContains("property1"); +verify.completionListContains("property2"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern11.ts b/tests/cases/fourslash/completionListInObjectBindingPattern11.ts new file mode 100644 index 0000000000000..25d6651a03855 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern11.ts @@ -0,0 +1,13 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////var { property1: prop1, /**/ }: I; + +goTo.marker(""); +verify.completionListContains("property2"); +verify.not.completionListContains("property1"); +verify.not.completionListContains("prop1"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern12.ts b/tests/cases/fourslash/completionListInObjectBindingPattern12.ts new file mode 100644 index 0000000000000..f31206a21b6d7 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern12.ts @@ -0,0 +1,13 @@ +/// + +////interface I { +//// property1: number; +//// property2: string; +////} +//// +////function f({ property1, /**/ }: I): void { +////} + +goTo.marker(""); +verify.completionListContains("property2"); +verify.not.completionListContains("property1"); \ No newline at end of file