Skip to content

Commit 9557e4a

Browse files
author
Andy
authored
Handle completions in interface / type literal similar to class (microsoft#22701)
* Handle completions in interface / type literal similar to class * Code review
1 parent 3837518 commit 9557e4a

11 files changed

+121
-144
lines changed

src/compiler/types.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ namespace ts {
990990

991991
export interface MethodSignature extends SignatureDeclarationBase, TypeElement {
992992
kind: SyntaxKind.MethodSignature;
993-
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
993+
parent?: ObjectTypeDeclaration;
994994
name: PropertyName;
995995
}
996996

@@ -1045,7 +1045,7 @@ namespace ts {
10451045

10461046
export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement {
10471047
kind: SyntaxKind.IndexSignature;
1048-
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
1048+
parent?: ObjectTypeDeclaration;
10491049
}
10501050

10511051
export interface TypeNode extends Node {
@@ -2026,6 +2026,8 @@ namespace ts {
20262026
block: Block;
20272027
}
20282028

2029+
export type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
2030+
20292031
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
20302032

20312033
export interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {

src/compiler/utilities.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ namespace ts {
948948
case SyntaxKind.ClassDeclaration:
949949
case SyntaxKind.ClassExpression:
950950
case SyntaxKind.TypeLiteral:
951-
return (<ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode>node).members;
951+
return (<ObjectTypeDeclaration>node).members;
952952
case SyntaxKind.ObjectLiteralExpression:
953953
return (<ObjectLiteralExpression>node).properties;
954954
}
@@ -3910,6 +3910,10 @@ namespace ts {
39103910
seen.set(key, true);
39113911
return true;
39123912
}
3913+
3914+
export function isObjectTypeDeclaration(node: Node): node is ObjectTypeDeclaration {
3915+
return isClassLike(node) || isInterfaceDeclaration(node) || isTypeLiteralNode(node);
3916+
}
39133917
}
39143918

39153919
namespace ts {

src/services/completions.ts

+85-107
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ namespace ts.Completions {
1818

1919
const enum KeywordCompletionFilters {
2020
None,
21-
ClassElementKeywords, // Keywords at class keyword
21+
ClassElementKeywords, // Keywords inside class body
22+
InterfaceElementKeywords, // Keywords inside interface body
2223
ConstructorParameterKeywords, // Keywords at constructor parameter
2324
FunctionLikeBodyKeywords, // Keywords at function like body
2425
TypeKeywords,
@@ -1527,58 +1528,51 @@ namespace ts.Completions {
15271528
* Relevant symbols are stored in the captured 'symbols' variable.
15281529
*/
15291530
function tryGetClassLikeCompletionSymbols(): GlobalsSearch {
1530-
const classLikeDeclaration = tryGetClassLikeCompletionContainer(contextToken);
1531-
if (!classLikeDeclaration) return GlobalsSearch.Continue;
1531+
const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location);
1532+
if (!decl) return GlobalsSearch.Continue;
15321533

15331534
// We're looking up possible property names from parent type.
15341535
completionKind = CompletionKind.MemberLike;
15351536
// Declaring new property/method/accessor
15361537
isNewIdentifierLocation = true;
15371538
// Has keywords for class elements
1538-
keywordFilters = KeywordCompletionFilters.ClassElementKeywords;
1539+
keywordFilters = isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords;
15391540

1540-
const baseTypeNode = getClassExtendsHeritageClauseElement(classLikeDeclaration);
1541-
const implementsTypeNodes = getClassImplementsHeritageClauseElements(classLikeDeclaration);
1542-
if (baseTypeNode || implementsTypeNodes) {
1543-
const classElement = contextToken.parent;
1544-
let classElementModifierFlags = isClassElement(classElement) && getModifierFlags(classElement);
1541+
// If you're in an interface you don't want to repeat things from super-interface. So just stop here.
1542+
if (!isClassLike(decl)) return GlobalsSearch.Success;
1543+
1544+
const baseTypeNode = getClassExtendsHeritageClauseElement(decl);
1545+
const implementsTypeNodes = getClassImplementsHeritageClauseElements(decl);
1546+
if (!baseTypeNode && !implementsTypeNodes) return GlobalsSearch.Success;
1547+
1548+
const classElement = contextToken.parent;
1549+
const classElementModifierFlags = (isClassElement(classElement) ? getModifierFlags(classElement) : ModifierFlags.None)
15451550
// If this is context token is not something we are editing now, consider if this would lead to be modifier
1546-
if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) {
1547-
switch (contextToken.getText()) {
1548-
case "private":
1549-
classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private;
1550-
break;
1551-
case "static":
1552-
classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static;
1553-
break;
1554-
}
1555-
}
1551+
| (isIdentifier(contextToken) && !isCurrentlyEditingNode(contextToken) ? modifierToFlag(contextToken.originalKeywordKind) : ModifierFlags.None);
15561552

1557-
// No member list for private methods
1558-
if (!(classElementModifierFlags & ModifierFlags.Private)) {
1559-
let baseClassTypeToGetPropertiesFrom: Type;
1560-
if (baseTypeNode) {
1561-
baseClassTypeToGetPropertiesFrom = typeChecker.getTypeAtLocation(baseTypeNode);
1562-
if (classElementModifierFlags & ModifierFlags.Static) {
1563-
// Use static class to get property symbols from
1564-
baseClassTypeToGetPropertiesFrom = typeChecker.getTypeOfSymbolAtLocation(
1565-
baseClassTypeToGetPropertiesFrom.symbol, classLikeDeclaration);
1566-
}
1567-
}
1568-
const implementedInterfaceTypePropertySymbols = (classElementModifierFlags & ModifierFlags.Static) ?
1569-
emptyArray :
1570-
flatMap(implementsTypeNodes || emptyArray, typeNode => typeChecker.getPropertiesOfType(typeChecker.getTypeAtLocation(typeNode)));
1571-
1572-
// List of property symbols of base type that are not private and already implemented
1573-
symbols = filterClassMembersList(
1574-
baseClassTypeToGetPropertiesFrom ?
1575-
typeChecker.getPropertiesOfType(baseClassTypeToGetPropertiesFrom) :
1576-
emptyArray,
1577-
implementedInterfaceTypePropertySymbols,
1578-
classLikeDeclaration.members,
1579-
classElementModifierFlags);
1553+
// No member list for private methods
1554+
if (classElementModifierFlags & ModifierFlags.Private) return GlobalsSearch.Success;
1555+
1556+
let baseClassTypeToGetPropertiesFrom: Type | undefined;
1557+
if (baseTypeNode) {
1558+
baseClassTypeToGetPropertiesFrom = typeChecker.getTypeAtLocation(baseTypeNode);
1559+
if (classElementModifierFlags & ModifierFlags.Static) {
1560+
// Use static class to get property symbols from
1561+
baseClassTypeToGetPropertiesFrom = typeChecker.getTypeOfSymbolAtLocation(baseClassTypeToGetPropertiesFrom.symbol, decl);
15801562
}
15811563
}
1564+
1565+
const implementedInterfaceTypePropertySymbols = !implementsTypeNodes || (classElementModifierFlags & ModifierFlags.Static)
1566+
? emptyArray
1567+
: flatMap(implementsTypeNodes, typeNode => typeChecker.getPropertiesOfType(typeChecker.getTypeAtLocation(typeNode)));
1568+
1569+
// List of property symbols of base type that are not private and already implemented
1570+
symbols = filterClassMembersList(
1571+
baseClassTypeToGetPropertiesFrom ? typeChecker.getPropertiesOfType(baseClassTypeToGetPropertiesFrom) : emptyArray,
1572+
implementedInterfaceTypePropertySymbols,
1573+
decl.members,
1574+
classElementModifierFlags);
1575+
15821576
return GlobalsSearch.Success;
15831577
}
15841578

@@ -1622,10 +1616,6 @@ namespace ts.Completions {
16221616
return undefined;
16231617
}
16241618

1625-
function isFromClassElementDeclaration(node: Node) {
1626-
return node.parent && isClassElement(node.parent) && isClassLike(node.parent.parent);
1627-
}
1628-
16291619
function isParameterOfConstructorDeclaration(node: Node) {
16301620
return isParameter(node) && isConstructorDeclaration(node.parent);
16311621
}
@@ -1636,56 +1626,6 @@ namespace ts.Completions {
16361626
(isConstructorParameterCompletionKeyword(node.kind) || isDeclarationName(node));
16371627
}
16381628

1639-
/**
1640-
* Returns the immediate owning class declaration of a context token,
1641-
* on the condition that one exists and that the context implies completion should be given.
1642-
*/
1643-
function tryGetClassLikeCompletionContainer(contextToken: Node): ClassLikeDeclaration {
1644-
if (contextToken) {
1645-
switch (contextToken.kind) {
1646-
case SyntaxKind.OpenBraceToken: // class c { |
1647-
if (isClassLike(contextToken.parent)) {
1648-
return contextToken.parent;
1649-
}
1650-
break;
1651-
1652-
// class c {getValue(): number, | }
1653-
case SyntaxKind.CommaToken:
1654-
if (isClassLike(contextToken.parent)) {
1655-
return contextToken.parent;
1656-
}
1657-
break;
1658-
1659-
// class c {getValue(): number; | }
1660-
case SyntaxKind.SemicolonToken:
1661-
// class c { method() { } | }
1662-
case SyntaxKind.CloseBraceToken:
1663-
if (isClassLike(location)) {
1664-
return location;
1665-
}
1666-
// class c { method() { } b| }
1667-
if (isFromClassElementDeclaration(location) &&
1668-
(location.parent as ClassElement).name === location) {
1669-
return location.parent.parent as ClassLikeDeclaration;
1670-
}
1671-
break;
1672-
1673-
default:
1674-
if (isFromClassElementDeclaration(contextToken) &&
1675-
(isClassMemberCompletionKeyword(contextToken.kind) ||
1676-
isClassMemberCompletionKeywordText(contextToken.getText()))) {
1677-
return contextToken.parent.parent as ClassLikeDeclaration;
1678-
}
1679-
}
1680-
}
1681-
1682-
// class c { method() { } | method2() { } }
1683-
if (location && location.kind === SyntaxKind.SyntaxList && isClassLike(location.parent)) {
1684-
return location.parent;
1685-
}
1686-
return undefined;
1687-
}
1688-
16891629
/**
16901630
* Returns the immediate owning class declaration of a context token,
16911631
* on the condition that one exists and that the context implies completion should be given.
@@ -1820,15 +1760,7 @@ namespace ts.Completions {
18201760
isFunctionLikeButNotConstructor(containingNodeKind);
18211761

18221762
case SyntaxKind.OpenBraceToken:
1823-
return containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { |
1824-
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface a { |
1825-
containingNodeKind === SyntaxKind.TypeLiteral; // const x : { |
1826-
1827-
case SyntaxKind.SemicolonToken:
1828-
return containingNodeKind === SyntaxKind.PropertySignature &&
1829-
contextToken.parent && contextToken.parent.parent &&
1830-
(contextToken.parent.parent.kind === SyntaxKind.InterfaceDeclaration || // interface a { f; |
1831-
contextToken.parent.parent.kind === SyntaxKind.TypeLiteral); // const x : { a; |
1763+
return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { |
18321764

18331765
case SyntaxKind.LessThanToken:
18341766
return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< |
@@ -1857,7 +1789,7 @@ namespace ts.Completions {
18571789

18581790
case SyntaxKind.GetKeyword:
18591791
case SyntaxKind.SetKeyword:
1860-
if (isFromClassElementDeclaration(contextToken)) {
1792+
if (isFromObjectTypeDeclaration(contextToken)) {
18611793
return false;
18621794
}
18631795
// falls through
@@ -1877,7 +1809,7 @@ namespace ts.Completions {
18771809
// If the previous token is keyword correspoding to class member completion keyword
18781810
// there will be completion available here
18791811
if (isClassMemberCompletionKeywordText(contextToken.getText()) &&
1880-
isFromClassElementDeclaration(contextToken)) {
1812+
isFromObjectTypeDeclaration(contextToken)) {
18811813
return false;
18821814
}
18831815

@@ -2162,6 +2094,8 @@ namespace ts.Completions {
21622094
return kind !== SyntaxKind.UndefinedKeyword;
21632095
case KeywordCompletionFilters.ClassElementKeywords:
21642096
return isClassMemberCompletionKeyword(kind);
2097+
case KeywordCompletionFilters.InterfaceElementKeywords:
2098+
return isInterfaceOrTypeLiteralCompletionKeyword(kind);
21652099
case KeywordCompletionFilters.ConstructorParameterKeywords:
21662100
return isConstructorParameterCompletionKeyword(kind);
21672101
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
@@ -2174,6 +2108,10 @@ namespace ts.Completions {
21742108
}));
21752109
}
21762110

2111+
function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean {
2112+
return kind === SyntaxKind.ReadonlyKeyword;
2113+
}
2114+
21772115
function isClassMemberCompletionKeyword(kind: SyntaxKind) {
21782116
switch (kind) {
21792117
case SyntaxKind.PublicKeyword:
@@ -2282,4 +2220,44 @@ namespace ts.Completions {
22822220
!(memberType.flags & TypeFlags.Primitive || checker.isArrayLikeType(memberType) || typeHasCallOrConstructSignatures(memberType, checker)));
22832221
return Debug.assertEachDefined(checker.getAllPossiblePropertiesOfTypes(filteredTypes), "getAllPossiblePropertiesOfTypes() should all be defined");
22842222
}
2223+
2224+
/**
2225+
* Returns the immediate owning class declaration of a context token,
2226+
* on the condition that one exists and that the context implies completion should be given.
2227+
*/
2228+
function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node): ObjectTypeDeclaration | undefined {
2229+
// class c { method() { } | method2() { } }
2230+
switch (location.kind) {
2231+
case SyntaxKind.SyntaxList:
2232+
return tryCast(location.parent, isObjectTypeDeclaration);
2233+
case SyntaxKind.EndOfFileToken:
2234+
const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration);
2235+
if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) {
2236+
return cls;
2237+
}
2238+
}
2239+
2240+
if (!contextToken) return undefined;
2241+
switch (contextToken.kind) {
2242+
case SyntaxKind.SemicolonToken: // class c {getValue(): number; | }
2243+
case SyntaxKind.CloseBraceToken: // class c { method() { } | }
2244+
// class c { method() { } b| }
2245+
return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location
2246+
? location.parent.parent as ObjectTypeDeclaration
2247+
: tryCast(location, isObjectTypeDeclaration);
2248+
case SyntaxKind.OpenBraceToken: // class c { |
2249+
case SyntaxKind.CommaToken: // class c {getValue(): number, | }
2250+
return tryCast(contextToken.parent, isObjectTypeDeclaration);
2251+
default:
2252+
if (!isFromObjectTypeDeclaration(contextToken)) return undefined;
2253+
const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword;
2254+
return (isValidKeyword(contextToken.kind) || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)))
2255+
? contextToken.parent.parent as ObjectTypeDeclaration : undefined;
2256+
}
2257+
}
2258+
2259+
// TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes
2260+
function isFromObjectTypeDeclaration(node: Node): boolean {
2261+
return node.parent && (isClassElement(node.parent) || isTypeElement(node.parent)) && isObjectTypeDeclaration(node.parent.parent);
2262+
}
22852263
}

tests/baselines/reference/api/tsserverlibrary.d.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ declare namespace ts {
655655
}
656656
interface MethodSignature extends SignatureDeclarationBase, TypeElement {
657657
kind: SyntaxKind.MethodSignature;
658-
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
658+
parent?: ObjectTypeDeclaration;
659659
name: PropertyName;
660660
}
661661
interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer {
@@ -689,7 +689,7 @@ declare namespace ts {
689689
type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration;
690690
interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement {
691691
kind: SyntaxKind.IndexSignature;
692-
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
692+
parent?: ObjectTypeDeclaration;
693693
}
694694
interface TypeNode extends Node {
695695
_typeNodeBrand: any;
@@ -1266,6 +1266,7 @@ declare namespace ts {
12661266
variableDeclaration?: VariableDeclaration;
12671267
block: Block;
12681268
}
1269+
type ObjectTypeDeclaration = ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
12691270
type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
12701271
interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer {
12711272
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;

0 commit comments

Comments
 (0)