From 7bac82f8cd3c26c94c30d87c8e02677b62f8fdf1 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 19 Mar 2018 14:33:55 -0700 Subject: [PATCH 1/2] Handle completions in interface / type literal similar to class --- src/compiler/types.ts | 6 +- src/compiler/utilities.ts | 6 +- src/services/completions.ts | 174 +++++++----------- .../reference/api/tsserverlibrary.d.ts | 5 +- tests/baselines/reference/api/typescript.d.ts | 5 +- .../completionEntryForClassMembers.ts | 2 +- ...ierDefinitionLocations_interfaceMembers.ts | 7 - ...erDefinitionLocations_interfaceMembers2.ts | 7 - .../fourslash/completionsInterfaceElement.ts | 11 ++ 9 files changed, 97 insertions(+), 126 deletions(-) delete mode 100644 tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers.ts delete mode 100644 tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers2.ts create mode 100644 tests/cases/fourslash/completionsInterfaceElement.ts diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7ed0f30916c6e..618f652b4909c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -989,7 +989,7 @@ namespace ts { export interface MethodSignature extends SignatureDeclarationBase, TypeElement { kind: SyntaxKind.MethodSignature; - parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; + parent?: ObjectTypeDeclaration; name: PropertyName; } @@ -1044,7 +1044,7 @@ namespace ts { export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; + parent?: ObjectTypeDeclaration; } export interface TypeNode extends Node { @@ -2023,6 +2023,8 @@ namespace ts { block: Block; } + export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; + export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 235b10e4cf19d..841b3541656e3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -948,7 +948,7 @@ namespace ts { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.TypeLiteral: - return (node).members; + return (node).members; case SyntaxKind.ObjectLiteralExpression: return (node).properties; } @@ -3876,6 +3876,10 @@ namespace ts { seen.set(key, true); return true; } + + export function isObjectTypeDeclaration(node: Node): node is ObjectTypeDeclaration { + return isClassLike(node) || isInterfaceDeclaration(node) || isTypeLiteralNode(node); + } } namespace ts { diff --git a/src/services/completions.ts b/src/services/completions.ts index 7d607b6618783..3b708976ec101 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -18,7 +18,8 @@ namespace ts.Completions { const enum KeywordCompletionFilters { None, - ClassElementKeywords, // Keywords at class keyword + ClassElementKeywords, // Keywords inside class body + InterfaceElementKeywords, // Keywords inside interface body ConstructorParameterKeywords, // Keywords at constructor parameter FunctionLikeBodyKeywords, // Keywords at function like body TypeKeywords, @@ -1051,7 +1052,6 @@ namespace ts.Completions { function tryGetGlobalSymbols(): boolean { let objectLikeContainer: ObjectLiteralExpression | BindingPattern; let namedImportsOrExports: NamedImportsOrExports; - let classLikeContainer: ClassLikeDeclaration; let jsxContainer: JsxOpeningLikeElement; if (objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken)) { @@ -1074,9 +1074,10 @@ namespace ts.Completions { return true; } - if (classLikeContainer = tryGetClassLikeCompletionContainer(contextToken)) { + const objectTypeContainer = contextToken && tryGetObjectTypeDeclarationCompletionContainer(contextToken, location); + if (objectTypeContainer) { // cursor inside class declaration - getGetClassLikeCompletionSymbols(classLikeContainer); + getObjectTypeConpletionSymbols(objectTypeContainer); return true; } @@ -1085,7 +1086,6 @@ namespace ts.Completions { if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) { // Cursor is inside a JSX self-closing element or opening element attrsType = typeChecker.getAllAttributesTypeFromJsxOpeningLikeElement(jsxContainer); - if (attrsType) { symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), jsxContainer.attributes.properties); completionKind = CompletionKind.MemberLike; @@ -1535,56 +1535,47 @@ namespace ts.Completions { * Aggregates relevant symbols for completion in class declaration * Relevant symbols are stored in the captured 'symbols' variable. */ - function getGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration) { + function getObjectTypeConpletionSymbols(decl: ObjectTypeDeclaration): void { // We're looking up possible property names from parent type. completionKind = CompletionKind.MemberLike; // Declaring new property/method/accessor isNewIdentifierLocation = true; // Has keywords for class elements - keywordFilters = KeywordCompletionFilters.ClassElementKeywords; + keywordFilters = isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords; + + // If you're in an interface you don't want to repeat things from super-interface. So just stop here. + if (!isClassLike(decl)) return; + + const baseTypeNode = getClassExtendsHeritageClauseElement(decl); + const implementsTypeNodes = getClassImplementsHeritageClauseElements(decl); + if (!baseTypeNode && !implementsTypeNodes) return; - const baseTypeNode = getClassExtendsHeritageClauseElement(classLikeDeclaration); - const implementsTypeNodes = getClassImplementsHeritageClauseElements(classLikeDeclaration); - if (baseTypeNode || implementsTypeNodes) { - const classElement = contextToken.parent; - let classElementModifierFlags = isClassElement(classElement) && getModifierFlags(classElement); + const classElement = contextToken.parent; + const classElementModifierFlags = (isClassElement(classElement) ? getModifierFlags(classElement) : ModifierFlags.None) // If this is context token is not something we are editing now, consider if this would lead to be modifier - if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { - switch (contextToken.getText()) { - case "private": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; - break; - case "static": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; - break; - } - } + | (isIdentifier(contextToken) && !isCurrentlyEditingNode(contextToken) ? modifierToFlag(contextToken.originalKeywordKind) : ModifierFlags.None); - // No member list for private methods - if (!(classElementModifierFlags & ModifierFlags.Private)) { - let baseClassTypeToGetPropertiesFrom: Type; - if (baseTypeNode) { - baseClassTypeToGetPropertiesFrom = typeChecker.getTypeAtLocation(baseTypeNode); - if (classElementModifierFlags & ModifierFlags.Static) { - // Use static class to get property symbols from - baseClassTypeToGetPropertiesFrom = typeChecker.getTypeOfSymbolAtLocation( - baseClassTypeToGetPropertiesFrom.symbol, classLikeDeclaration); - } - } - const implementedInterfaceTypePropertySymbols = (classElementModifierFlags & ModifierFlags.Static) ? - emptyArray : - flatMap(implementsTypeNodes || emptyArray, typeNode => typeChecker.getPropertiesOfType(typeChecker.getTypeAtLocation(typeNode))); - - // List of property symbols of base type that are not private and already implemented - symbols = filterClassMembersList( - baseClassTypeToGetPropertiesFrom ? - typeChecker.getPropertiesOfType(baseClassTypeToGetPropertiesFrom) : - emptyArray, - implementedInterfaceTypePropertySymbols, - classLikeDeclaration.members, - classElementModifierFlags); + // No member list for private methods + if (classElementModifierFlags & ModifierFlags.Private) return; + + let baseClassTypeToGetPropertiesFrom: Type | undefined; + if (baseTypeNode) { + baseClassTypeToGetPropertiesFrom = typeChecker.getTypeAtLocation(baseTypeNode); + if (classElementModifierFlags & ModifierFlags.Static) { + // Use static class to get property symbols from + baseClassTypeToGetPropertiesFrom = typeChecker.getTypeOfSymbolAtLocation(baseClassTypeToGetPropertiesFrom.symbol, decl); } } + const implementedInterfaceTypePropertySymbols = !implementsTypeNodes || (classElementModifierFlags & ModifierFlags.Static) + ? emptyArray + : flatMap(implementsTypeNodes, typeNode => typeChecker.getPropertiesOfType(typeChecker.getTypeAtLocation(typeNode))); + + // List of property symbols of base type that are not private and already implemented + symbols = filterClassMembersList( + baseClassTypeToGetPropertiesFrom ? typeChecker.getPropertiesOfType(baseClassTypeToGetPropertiesFrom) : emptyArray, + implementedInterfaceTypePropertySymbols, + decl.members, + classElementModifierFlags); } /** @@ -1627,10 +1618,6 @@ namespace ts.Completions { return undefined; } - function isFromClassElementDeclaration(node: Node) { - return node.parent && isClassElement(node.parent) && isClassLike(node.parent.parent); - } - function isParameterOfConstructorDeclaration(node: Node) { return isParameter(node) && isConstructorDeclaration(node.parent); } @@ -1641,56 +1628,6 @@ namespace ts.Completions { (isConstructorParameterCompletionKeyword(node.kind) || isDeclarationName(node)); } - /** - * Returns the immediate owning class declaration of a context token, - * on the condition that one exists and that the context implies completion should be given. - */ - function tryGetClassLikeCompletionContainer(contextToken: Node): ClassLikeDeclaration { - if (contextToken) { - switch (contextToken.kind) { - case SyntaxKind.OpenBraceToken: // class c { | - if (isClassLike(contextToken.parent)) { - return contextToken.parent; - } - break; - - // class c {getValue(): number, | } - case SyntaxKind.CommaToken: - if (isClassLike(contextToken.parent)) { - return contextToken.parent; - } - break; - - // class c {getValue(): number; | } - case SyntaxKind.SemicolonToken: - // class c { method() { } | } - case SyntaxKind.CloseBraceToken: - if (isClassLike(location)) { - return location; - } - // class c { method() { } b| } - if (isFromClassElementDeclaration(location) && - (location.parent as ClassElement).name === location) { - return location.parent.parent as ClassLikeDeclaration; - } - break; - - default: - if (isFromClassElementDeclaration(contextToken) && - (isClassMemberCompletionKeyword(contextToken.kind) || - isClassMemberCompletionKeywordText(contextToken.getText()))) { - return contextToken.parent.parent as ClassLikeDeclaration; - } - } - } - - // class c { method() { } | method2() { } } - if (location && location.kind === SyntaxKind.SyntaxList && isClassLike(location.parent)) { - return location.parent; - } - return undefined; - } - /** * Returns the immediate owning class declaration of a context token, * on the condition that one exists and that the context implies completion should be given. @@ -1825,9 +1762,7 @@ namespace ts.Completions { isFunctionLikeButNotConstructor(containingNodeKind); case SyntaxKind.OpenBraceToken: - return containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { | - containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface a { | - containingNodeKind === SyntaxKind.TypeLiteral; // const x : { | + return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | case SyntaxKind.SemicolonToken: return containingNodeKind === SyntaxKind.PropertySignature && @@ -1862,7 +1797,7 @@ namespace ts.Completions { case SyntaxKind.GetKeyword: case SyntaxKind.SetKeyword: - if (isFromClassElementDeclaration(contextToken)) { + if (isFromObjectTypeDeclaration(contextToken)) { return false; } // falls through @@ -1882,7 +1817,7 @@ namespace ts.Completions { // If the previous token is keyword correspoding to class member completion keyword // there will be completion available here if (isClassMemberCompletionKeywordText(contextToken.getText()) && - isFromClassElementDeclaration(contextToken)) { + isFromObjectTypeDeclaration(contextToken)) { return false; } @@ -2167,6 +2102,8 @@ namespace ts.Completions { return kind !== SyntaxKind.UndefinedKeyword; case KeywordCompletionFilters.ClassElementKeywords: return isClassMemberCompletionKeyword(kind); + case KeywordCompletionFilters.InterfaceElementKeywords: + return kind === SyntaxKind.ReadonlyKeyword; case KeywordCompletionFilters.ConstructorParameterKeywords: return isConstructorParameterCompletionKeyword(kind); case KeywordCompletionFilters.FunctionLikeBodyKeywords: @@ -2287,4 +2224,33 @@ namespace ts.Completions { !(memberType.flags & TypeFlags.Primitive || checker.isArrayLikeType(memberType) || typeHasCallOrConstructSignatures(memberType, checker))); return Debug.assertEachDefined(checker.getAllPossiblePropertiesOfTypes(filteredTypes), "getAllPossiblePropertiesOfTypes() should all be defined"); } + + /** + * Returns the immediate owning class declaration of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ + function tryGetObjectTypeDeclarationCompletionContainer(contextToken: Node, location: Node): ObjectTypeDeclaration | undefined { + // class c { method() { } | method2() { } } + if (location.kind === SyntaxKind.SyntaxList && isObjectTypeDeclaration(location.parent)) return location.parent; + switch (contextToken.kind) { + case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } + case SyntaxKind.CloseBraceToken: // class c { method() { } | } + // class c { method() { } b| } + return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location + ? location.parent.parent as ObjectTypeDeclaration + : tryCast(location, isClassLike); + case SyntaxKind.OpenBraceToken: // class c { | + case SyntaxKind.CommaToken: // class c {getValue(): number, | } + return tryCast(contextToken.parent, isObjectTypeDeclaration); + default: + return isFromObjectTypeDeclaration(contextToken) && + (isClassMemberCompletionKeyword(contextToken.kind) || isIdentifier(contextToken) && isClassMemberCompletionKeywordText(contextToken.text)) + ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; + } + } + + // TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes + function isFromObjectTypeDeclaration(node: Node): boolean { + return node.parent && (isClassElement(node.parent) || isTypeElement(node.parent)) && isObjectTypeDeclaration(node.parent.parent); + } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4bc0f5160d2cc..427f6f0c1df19 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -655,7 +655,7 @@ declare namespace ts { } interface MethodSignature extends SignatureDeclarationBase, TypeElement { kind: SyntaxKind.MethodSignature; - parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; + parent?: ObjectTypeDeclaration; name: PropertyName; } interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { @@ -689,7 +689,7 @@ declare namespace ts { type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; + parent?: ObjectTypeDeclaration; } interface TypeNode extends Node { _typeNodeBrand: any; @@ -1264,6 +1264,7 @@ declare namespace ts { variableDeclaration?: VariableDeclaration; block: Block; } + type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 1f64dd12ab8db..dc7a24cae8763 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -655,7 +655,7 @@ declare namespace ts { } interface MethodSignature extends SignatureDeclarationBase, TypeElement { kind: SyntaxKind.MethodSignature; - parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; + parent?: ObjectTypeDeclaration; name: PropertyName; } interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { @@ -689,7 +689,7 @@ declare namespace ts { type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { kind: SyntaxKind.IndexSignature; - parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; + parent?: ObjectTypeDeclaration; } interface TypeNode extends Node { _typeNodeBrand: any; @@ -1264,6 +1264,7 @@ declare namespace ts { variableDeclaration?: VariableDeclaration; block: Block; } + type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode; type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts index 8945245a286ed..1539823e22da0 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -114,7 +114,7 @@ ////} ////class O extends B { //// constructor(public a) { -//// }, +//// }, //// /*classElementAfterConstructorSeparatedByComma*/ ////} diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers.ts deleted file mode 100644 index 9998f38c25558..0000000000000 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// - -////var aa = 1; - -////interface a { /*interfaceValue1*/ - -goTo.eachMarker(() => verify.completionListIsEmpty()); diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers2.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers2.ts deleted file mode 100644 index 234e41bfb71ab..0000000000000 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers2.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// - -////var aa = 1; - -////interface a { f/*interfaceValue2*/ - -goTo.eachMarker(() => verify.completionListIsEmpty()); diff --git a/tests/cases/fourslash/completionsInterfaceElement.ts b/tests/cases/fourslash/completionsInterfaceElement.ts new file mode 100644 index 0000000000000..97af329cd876f --- /dev/null +++ b/tests/cases/fourslash/completionsInterfaceElement.ts @@ -0,0 +1,11 @@ +/// + +////const foo = 0; +////interface I { +//// m(): void; +//// fo/*i*/ +////} +////type T = { fo/*t*/ }; + +//verify.completionsAt("i", ["readonly"]); +verify.completionsAt("t", ["readonly"]); From a66926cec2b66212503c6d13655f4fcb14b47a38 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 23 Mar 2018 13:58:52 -0700 Subject: [PATCH 2/2] Code review --- src/services/completions.ts | 35 ++++++++++++------- .../completionEntryForClassMembers.ts | 2 +- ...erDefinitionLocations_interfaceMembers3.ts | 7 ---- ...nUnclosedObjectTypeLiteralInSignature04.ts | 8 +---- ...pletionListWithModulesInsideModuleScope.ts | 4 +-- .../fourslash/completionsInterfaceElement.ts | 11 ++++-- 6 files changed, 35 insertions(+), 32 deletions(-) delete mode 100644 tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers3.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index d0cde32b169ec..a51452ac87968 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1528,7 +1528,7 @@ namespace ts.Completions { * Relevant symbols are stored in the captured 'symbols' variable. */ function tryGetClassLikeCompletionSymbols(): GlobalsSearch { - const decl = contextToken && tryGetObjectTypeDeclarationCompletionContainer(contextToken, location); + const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location); if (!decl) return GlobalsSearch.Continue; // We're looking up possible property names from parent type. @@ -1762,12 +1762,6 @@ namespace ts.Completions { case SyntaxKind.OpenBraceToken: return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | - case SyntaxKind.SemicolonToken: - return containingNodeKind === SyntaxKind.PropertySignature && - contextToken.parent && contextToken.parent.parent && - (contextToken.parent.parent.kind === SyntaxKind.InterfaceDeclaration || // interface a { f; | - contextToken.parent.parent.kind === SyntaxKind.TypeLiteral); // const x : { a; | - case SyntaxKind.LessThanToken: return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< | containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< | @@ -2101,7 +2095,7 @@ namespace ts.Completions { case KeywordCompletionFilters.ClassElementKeywords: return isClassMemberCompletionKeyword(kind); case KeywordCompletionFilters.InterfaceElementKeywords: - return kind === SyntaxKind.ReadonlyKeyword; + return isInterfaceOrTypeLiteralCompletionKeyword(kind); case KeywordCompletionFilters.ConstructorParameterKeywords: return isConstructorParameterCompletionKeyword(kind); case KeywordCompletionFilters.FunctionLikeBodyKeywords: @@ -2114,6 +2108,10 @@ namespace ts.Completions { })); } + function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { + return kind === SyntaxKind.ReadonlyKeyword; + } + function isClassMemberCompletionKeyword(kind: SyntaxKind) { switch (kind) { case SyntaxKind.PublicKeyword: @@ -2227,22 +2225,33 @@ namespace ts.Completions { * Returns the immediate owning class declaration of a context token, * on the condition that one exists and that the context implies completion should be given. */ - function tryGetObjectTypeDeclarationCompletionContainer(contextToken: Node, location: Node): ObjectTypeDeclaration | undefined { + function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node): ObjectTypeDeclaration | undefined { // class c { method() { } | method2() { } } - if (location.kind === SyntaxKind.SyntaxList && isObjectTypeDeclaration(location.parent)) return location.parent; + switch (location.kind) { + case SyntaxKind.SyntaxList: + return tryCast(location.parent, isObjectTypeDeclaration); + case SyntaxKind.EndOfFileToken: + const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration); + if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { + return cls; + } + } + + if (!contextToken) return undefined; switch (contextToken.kind) { case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } case SyntaxKind.CloseBraceToken: // class c { method() { } | } // class c { method() { } b| } return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location ? location.parent.parent as ObjectTypeDeclaration - : tryCast(location, isClassLike); + : tryCast(location, isObjectTypeDeclaration); case SyntaxKind.OpenBraceToken: // class c { | case SyntaxKind.CommaToken: // class c {getValue(): number, | } return tryCast(contextToken.parent, isObjectTypeDeclaration); default: - return isFromObjectTypeDeclaration(contextToken) && - (isClassMemberCompletionKeyword(contextToken.kind) || isIdentifier(contextToken) && isClassMemberCompletionKeywordText(contextToken.text)) + if (!isFromObjectTypeDeclaration(contextToken)) return undefined; + const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; + return (isValidKeyword(contextToken.kind) || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text))) ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; } } diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts index 1539823e22da0..8945245a286ed 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -114,7 +114,7 @@ ////} ////class O extends B { //// constructor(public a) { -//// }, +//// }, //// /*classElementAfterConstructorSeparatedByComma*/ ////} diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers3.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers3.ts deleted file mode 100644 index f0a596ef8b1db..0000000000000 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_interfaceMembers3.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// - -////var aa = 1; - -////interface a { f; /*interfaceValue3*/ - -goTo.eachMarker(() => verify.completionListIsEmpty()); diff --git a/tests/cases/fourslash/completionListInUnclosedObjectTypeLiteralInSignature04.ts b/tests/cases/fourslash/completionListInUnclosedObjectTypeLiteralInSignature04.ts index 2c435890ebd73..d6746803ab871 100644 --- a/tests/cases/fourslash/completionListInUnclosedObjectTypeLiteralInSignature04.ts +++ b/tests/cases/fourslash/completionListInUnclosedObjectTypeLiteralInSignature04.ts @@ -7,10 +7,4 @@ //// ////declare function foo(obj: I): { /*1*/ -goTo.marker("1"); - -verify.not.completionListContains("I"); -verify.not.completionListContains("TString"); -verify.not.completionListContains("TNumber"); -verify.not.completionListContains("foo"); -verify.not.completionListContains("obj"); +verify.completionsAt("1", ["readonly"]); diff --git a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts index cb6bf283b6914..d13141c38cba0 100644 --- a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts +++ b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts @@ -311,7 +311,7 @@ goToMarkAndGeneralVerify('class', { isClassScope: true }); //verify.not.completionListContains('ceVar'); // from interface in mod1 -goToMarkAndGeneralVerify('interface', { insideMod1: true }); +verify.completionsAt("interface", ["readonly"]); // from namespace in mod1 verifyNamespaceInMod1('namespace'); @@ -348,7 +348,7 @@ verify.not.completionListContains('ceFunc'); verify.not.completionListContains('ceVar'); // from exported interface in mod1 -goToMarkAndGeneralVerify('exportedInterface', { insideMod1: true }); +verify.completionsAt("exportedInterface", ["readonly"]); // from exported namespace in mod1 verifyExportedNamespace('exportedNamespace'); diff --git a/tests/cases/fourslash/completionsInterfaceElement.ts b/tests/cases/fourslash/completionsInterfaceElement.ts index 97af329cd876f..fe7d55db7940e 100644 --- a/tests/cases/fourslash/completionsInterfaceElement.ts +++ b/tests/cases/fourslash/completionsInterfaceElement.ts @@ -5,7 +5,14 @@ //// m(): void; //// fo/*i*/ ////} +////interface J { /*j*/ } +////interface K { f; /*k*/ } + ////type T = { fo/*t*/ }; +////type U = { /*u*/ }; + +////interface EndOfFile { f; /*e*/ -//verify.completionsAt("i", ["readonly"]); -verify.completionsAt("t", ["readonly"]); +for (const marker of test.markerNames()) { + verify.completionsAt(marker, ["readonly"]); +}