Skip to content

Enable completions in object binding patterns #3615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 25, 2015
Merged
6 changes: 5 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11993,7 +11993,7 @@ namespace ts {
return resolveExternalModuleName(node, <LiteralExpression>node);
}

// Intentional fall-through
// fall through
case SyntaxKind.NumericLiteral:
// index access
if (node.parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>node.parent).argumentExpression === node) {
Expand Down Expand Up @@ -12060,6 +12060,10 @@ namespace ts {
return symbol && getTypeOfSymbol(symbol);
}

if (isBindingPattern(node)) {
return getTypeForVariableLikeDeclaration(<VariableLikeDeclaration>node.parent);
}

if (isInRightSideOfImportOrExportAssignment(<Identifier>node)) {
let symbol = getSymbolInfo(node);
let declaredType = symbol && getDeclaredTypeOfSymbol(symbol);
Expand Down
3 changes: 1 addition & 2 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}
}
Expand Down
83 changes: 53 additions & 30 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(<ObjectLiteralExpression>objectLikeContainer);
existingMembers = (<ObjectLiteralExpression>objectLikeContainer).properties;
}
else {
typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer);
existingMembers = (<BindingPattern>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)) {
Expand Down Expand Up @@ -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 <ObjectLiteralExpression>parent;
let parent = contextToken.parent;
if (parent && (parent.kind === SyntaxKind.ObjectLiteralExpression || parent.kind === SyntaxKind.ObjectBindingPattern)) {
return <ObjectLiteralExpression | BindingPattern>parent;
}
break;
}
Expand Down Expand Up @@ -3236,8 +3248,7 @@ namespace ts {
containingNodeKind === SyntaxKind.ClassDeclaration || // class A<T, |
containingNodeKind === SyntaxKind.FunctionDeclaration || // function A<T, |
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A<T, |
containingNodeKind === SyntaxKind.ArrayBindingPattern || // var [x, y|
containingNodeKind === SyntaxKind.ObjectBindingPattern; // function func({ x, y|
containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x, y|

case SyntaxKind.DotToken:
return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.|
Expand All @@ -3255,8 +3266,7 @@ namespace ts {
case SyntaxKind.OpenBraceToken:
return containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { |
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface a { |
containingNodeKind === SyntaxKind.TypeLiteral || // let x : { |
containingNodeKind === SyntaxKind.ObjectBindingPattern; // function func({ x|
containingNodeKind === SyntaxKind.TypeLiteral; // let x : { |

case SyntaxKind.SemicolonToken:
return containingNodeKind === SyntaxKind.PropertySignature &&
Expand Down Expand Up @@ -3348,26 +3358,39 @@ namespace ts {
return filter(exports, e => !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<boolean> = {};
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[(<Identifier>m.name).text] = true;
});
let existingName: string;

if (m.kind === SyntaxKind.BindingElement && (<BindingElement>m).propertyName) {
existingName = (<BindingElement>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 = (<Identifier>m.name).text;
}

existingMemberNames[existingName] = true;
}

let filteredMembers: Symbol[] = [];
forEach(contextualMemberSymbols, s => {
Expand Down
13 changes: 13 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern01.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////var foo: I;
////var { /**/ } = foo;

goTo.marker();
verify.completionListContains("property1");
verify.completionListContains("property2");
13 changes: 13 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern02.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////var foo: I;
////var { property1, /**/ } = foo;

goTo.marker();
verify.completionListContains("property2");
verify.not.completionListContains("property1");
13 changes: 13 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern03.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////var foo: I;
////var { property1: /**/ } = foo;

goTo.marker();
verify.completionListAllowsNewIdentifier();
verify.completionListIsEmpty();
13 changes: 13 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern04.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////var foo: I;
////var { prope/**/ } = foo;

goTo.marker();
verify.completionListContains("property1");
verify.completionListContains("property2");
12 changes: 12 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern05.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////var foo: I;
////var { property1/**/ } = foo;

goTo.marker();
verify.completionListContains("property1");
12 changes: 12 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern06.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////var foo: I;
////var { property1, property2, /**/ } = foo;

goTo.marker();
verify.completionListIsEmpty();
18 changes: 18 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern07.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts'/>

////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");
18 changes: 18 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern08.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts'/>

////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");
19 changes: 19 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern09.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// <reference path='fourslash.ts'/>

////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");
23 changes: 23 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern10.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts'/>

////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");
13 changes: 13 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////var { property1: prop1, /**/ }: I;

goTo.marker("");
verify.completionListContains("property2");
verify.not.completionListContains("property1");
verify.not.completionListContains("prop1");
13 changes: 13 additions & 0 deletions tests/cases/fourslash/completionListInObjectBindingPattern12.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>

////interface I {
//// property1: number;
//// property2: string;
////}
////
////function f({ property1, /**/ }: I): void {
////}

goTo.marker("");
verify.completionListContains("property2");
verify.not.completionListContains("property1");