From 7fb821e45b1bf0192728d7085ffe91399e5d7a49 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 12 Jun 2017 12:12:51 -0700 Subject: [PATCH] Support completions in destructuring in for-of (#16454) --- src/services/completions.ts | 55 +++++++++---------- .../fourslash/completionsDestructuring.ts | 12 ++++ 2 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 tests/cases/fourslash/completionsDestructuring.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 4a6b6170b78..071496a4224 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -844,7 +844,7 @@ namespace ts.Completions { * * @returns true if 'symbols' was successfully populated; false otherwise. */ - function tryGetObjectLikeCompletionSymbols(objectLikeContainer: ObjectLiteralExpression | BindingPattern): boolean { + function tryGetObjectLikeCompletionSymbols(objectLikeContainer: ObjectLiteralExpression | ObjectBindingPattern): boolean { // We're looking up possible property names from contextual/inferred/declared type. isMemberCompletion = true; @@ -860,41 +860,36 @@ namespace ts.Completions { typeMembers = typeChecker.getAllPossiblePropertiesOfType(typeForObject); existingMembers = (objectLikeContainer).properties; } - else if (objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern) { + else { + Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern); // We are *only* completing on properties from the type being destructured. isNewIdentifierLocation = false; const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); - if (isVariableLike(rootDeclaration)) { - // We don't want to complete using the type acquired by the shape - // of the binding pattern; we are only interested in types acquired - // through type declaration or inference. - // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - - // type of parameter will flow in from the contextual type of the function - let canGetType = !!(rootDeclaration.initializer || rootDeclaration.type); - if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { - if (isExpression(rootDeclaration.parent)) { - canGetType = !!typeChecker.getContextualType(rootDeclaration.parent); - } - else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { - canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent); - } + if (!isVariableLike(rootDeclaration)) throw Debug.fail("Root declaration is not variable-like."); + + // We don't want to complete using the type acquired by the shape + // of the binding pattern; we are only interested in types acquired + // through type declaration or inference. + // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - + // type of parameter will flow in from the contextual type of the function + let canGetType = rootDeclaration.initializer || rootDeclaration.type || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; + if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { + if (isExpression(rootDeclaration.parent)) { + canGetType = !!typeChecker.getContextualType(rootDeclaration.parent); } - if (canGetType) { - const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); - if (!typeForObject) return false; - // In a binding pattern, get only known properties. Everywhere else we will get all possible properties. - typeMembers = typeChecker.getPropertiesOfType(typeForObject); - existingMembers = (objectLikeContainer).elements; + else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { + canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent); } } - else { - Debug.fail("Root declaration is not variable-like."); + if (canGetType) { + const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); + if (!typeForObject) return false; + // In a binding pattern, get only known properties. Everywhere else we will get all possible properties. + typeMembers = typeChecker.getPropertiesOfType(typeForObject); + existingMembers = (objectLikeContainer).elements; } } - else { - Debug.fail("Expected object literal or binding pattern, got " + objectLikeContainer.kind); - } if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list @@ -1003,14 +998,14 @@ namespace ts.Completions { * 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 { + function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | ObjectBindingPattern { if (contextToken) { switch (contextToken.kind) { case SyntaxKind.OpenBraceToken: // const x = { | case SyntaxKind.CommaToken: // const x = { a: 0, | const parent = contextToken.parent; - if (parent && (parent.kind === SyntaxKind.ObjectLiteralExpression || parent.kind === SyntaxKind.ObjectBindingPattern)) { - return parent; + if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { + return parent; } break; } diff --git a/tests/cases/fourslash/completionsDestructuring.ts b/tests/cases/fourslash/completionsDestructuring.ts new file mode 100644 index 00000000000..d2e9f38871e --- /dev/null +++ b/tests/cases/fourslash/completionsDestructuring.ts @@ -0,0 +1,12 @@ +/// + +////const points = [{ x: 1, y: 2 }]; +////points.forEach(({ /*a*/ }) => { }); +////const { /*b*/ } = points[0]; +////for (const { /*c*/ } of points) {} + +goTo.eachMarker(() => { + verify.completionListContains("x"); + verify.completionListContains("y"); + verify.completionListCount(2); +});