Handle completions in interface / type literal similar to class (#22701)

* Handle completions in interface / type literal similar to class

* Code review
This commit is contained in:
Andy
2018-03-23 16:04:29 -07:00
committed by GitHub
parent 383751866e
commit 9557e4ad96
11 changed files with 120 additions and 143 deletions

View File

@@ -990,7 +990,7 @@ namespace ts {
export interface MethodSignature extends SignatureDeclarationBase, TypeElement {
kind: SyntaxKind.MethodSignature;
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
parent?: ObjectTypeDeclaration;
name: PropertyName;
}
@@ -1045,7 +1045,7 @@ namespace ts {
export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement {
kind: SyntaxKind.IndexSignature;
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
parent?: ObjectTypeDeclaration;
}
export interface TypeNode extends Node {
@@ -2026,6 +2026,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 {

View File

@@ -948,7 +948,7 @@ namespace ts {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.TypeLiteral:
return (<ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode>node).members;
return (<ObjectTypeDeclaration>node).members;
case SyntaxKind.ObjectLiteralExpression:
return (<ObjectLiteralExpression>node).properties;
}
@@ -3910,6 +3910,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 {

View File

@@ -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,
@@ -1527,58 +1528,51 @@ namespace ts.Completions {
* Relevant symbols are stored in the captured 'symbols' variable.
*/
function tryGetClassLikeCompletionSymbols(): GlobalsSearch {
const classLikeDeclaration = tryGetClassLikeCompletionContainer(contextToken);
if (!classLikeDeclaration) return GlobalsSearch.Continue;
const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location);
if (!decl) return GlobalsSearch.Continue;
// 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;
const baseTypeNode = getClassExtendsHeritageClauseElement(classLikeDeclaration);
const implementsTypeNodes = getClassImplementsHeritageClauseElements(classLikeDeclaration);
if (baseTypeNode || implementsTypeNodes) {
const classElement = contextToken.parent;
let classElementModifierFlags = isClassElement(classElement) && getModifierFlags(classElement);
// If you're in an interface you don't want to repeat things from super-interface. So just stop here.
if (!isClassLike(decl)) return GlobalsSearch.Success;
const baseTypeNode = getClassExtendsHeritageClauseElement(decl);
const implementsTypeNodes = getClassImplementsHeritageClauseElements(decl);
if (!baseTypeNode && !implementsTypeNodes) return GlobalsSearch.Success;
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)));
// No member list for private methods
if (classElementModifierFlags & ModifierFlags.Private) return GlobalsSearch.Success;
// 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);
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);
return GlobalsSearch.Success;
}
@@ -1622,10 +1616,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);
}
@@ -1636,56 +1626,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.
@@ -1820,15 +1760,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 : { |
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; |
return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { |
case SyntaxKind.LessThanToken:
return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< |
@@ -1857,7 +1789,7 @@ namespace ts.Completions {
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
if (isFromClassElementDeclaration(contextToken)) {
if (isFromObjectTypeDeclaration(contextToken)) {
return false;
}
// falls through
@@ -1877,7 +1809,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;
}
@@ -2162,6 +2094,8 @@ namespace ts.Completions {
return kind !== SyntaxKind.UndefinedKeyword;
case KeywordCompletionFilters.ClassElementKeywords:
return isClassMemberCompletionKeyword(kind);
case KeywordCompletionFilters.InterfaceElementKeywords:
return isInterfaceOrTypeLiteralCompletionKeyword(kind);
case KeywordCompletionFilters.ConstructorParameterKeywords:
return isConstructorParameterCompletionKeyword(kind);
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
@@ -2174,6 +2108,10 @@ namespace ts.Completions {
}));
}
function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean {
return kind === SyntaxKind.ReadonlyKeyword;
}
function isClassMemberCompletionKeyword(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.PublicKeyword:
@@ -2282,4 +2220,44 @@ 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(sourceFile: SourceFile, contextToken: Node | undefined, location: Node): ObjectTypeDeclaration | undefined {
// class c { method() { } | method2() { } }
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, isObjectTypeDeclaration);
case SyntaxKind.OpenBraceToken: // class c { |
case SyntaxKind.CommaToken: // class c {getValue(): number, | }
return tryCast(contextToken.parent, isObjectTypeDeclaration);
default:
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;
}
}
// 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);
}
}

View File

@@ -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;
@@ -1266,6 +1266,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;

View File

@@ -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;
@@ -1266,6 +1266,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;

View File

@@ -1,7 +0,0 @@
/// <reference path='fourslash.ts' />
////var aa = 1;
////interface a { /*interfaceValue1*/
goTo.eachMarker(() => verify.completionListIsEmpty());

View File

@@ -1,7 +0,0 @@
/// <reference path='fourslash.ts' />
////var aa = 1;
////interface a { f/*interfaceValue2*/
goTo.eachMarker(() => verify.completionListIsEmpty());

View File

@@ -1,7 +0,0 @@
/// <reference path='fourslash.ts' />
////var aa = 1;
////interface a { f; /*interfaceValue3*/
goTo.eachMarker(() => verify.completionListIsEmpty());

View File

@@ -7,10 +7,4 @@
////
////declare function foo<TString, TNumber>(obj: I<TString, TNumber>): { /*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"]);

View File

@@ -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');

View File

@@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />
////const foo = 0;
////interface I {
//// m(): void;
//// fo/*i*/
////}
////interface J { /*j*/ }
////interface K { f; /*k*/ }
////type T = { fo/*t*/ };
////type U = { /*u*/ };
////interface EndOfFile { f; /*e*/
for (const marker of test.markerNames()) {
verify.completionsAt(marker, ["readonly"]);
}