Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ namespace ts {

export interface MethodSignature extends SignatureDeclarationBase, TypeElement {
kind: SyntaxKind.MethodSignature;
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
parent?: ObjectTypeDeclaration;
name: PropertyName;
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down
192 changes: 85 additions & 107 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)));

// 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 GlobalsSearch.Success;

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;
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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.
Expand Down Expand Up @@ -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< |
Expand Down Expand Up @@ -1857,7 +1789,7 @@ namespace ts.Completions {

case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
if (isFromClassElementDeclaration(contextToken)) {
if (isFromObjectTypeDeclaration(contextToken)) {
return false;
}
// falls through
Expand All @@ -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;
}

Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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);
}
}
5 changes: 3 additions & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading