diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1022e6c11eb..1cc3add43e3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2111,6 +2111,13 @@ namespace ts { return heritageClause ? heritageClause.types : undefined; } + /** Returns the node in an `extends` or `implements` clause of a class or interface. */ + export function getAllSuperTypeNodes(node: Node): ReadonlyArray { + return isInterfaceDeclaration(node) ? getInterfaceBaseTypeNodes(node) || emptyArray + : isClassLike(node) ? concatenate(singleElementArray(getClassExtendsHeritageClauseElement(node)), getClassImplementsHeritageClauseElements(node)) || emptyArray + : emptyArray; + } + export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration) { const heritageClause = getHeritageClause(node.heritageClauses, SyntaxKind.ExtendsKeyword); return heritageClause ? heritageClause.types : undefined; diff --git a/src/services/completions.ts b/src/services/completions.ts index e7bbb47513f..4284de589bc 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1563,43 +1563,34 @@ namespace ts.Completions { completionKind = CompletionKind.MemberLike; // Declaring new property/method/accessor isNewIdentifierLocation = true; - // Has keywords for class elements 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 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 context token is not something we are editing now, consider if this would lead to be modifier - | (!isCurrentlyEditingNode(contextToken) ? modifierToFlag(keywordForNode(contextToken)) : ModifierFlags.None); - - // 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); + let classElementModifierFlags = isClassElement(classElement) && getModifierFlags(classElement); + // 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; } } - 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); + // No member list for private methods + if (!(classElementModifierFlags & ModifierFlags.Private)) { + // List of property symbols of base type that are not private and already implemented + const baseSymbols = flatMap(getAllSuperTypeNodes(decl), baseTypeNode => { + const type = typeChecker.getTypeAtLocation(baseTypeNode); + return typeChecker.getPropertiesOfType(classElementModifierFlags & ModifierFlags.Static ? typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl) : type); + }); + symbols = filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags); + } return GlobalsSearch.Success; } @@ -1967,12 +1958,8 @@ namespace ts.Completions { * * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags */ - function filterClassMembersList( - baseSymbols: ReadonlyArray, - implementingTypeSymbols: ReadonlyArray, - existingMembers: ReadonlyArray, - currentClassElementModifierFlags: ModifierFlags): Symbol[] { - const existingMemberNames = createUnderscoreEscapedMap(); + function filterClassMembersList(baseSymbols: ReadonlyArray, existingMembers: ReadonlyArray, currentClassElementModifierFlags: ModifierFlags): Symbol[] { + const existingMemberNames = createUnderscoreEscapedMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members if (m.kind !== SyntaxKind.PropertyDeclaration && @@ -1993,10 +1980,7 @@ namespace ts.Completions { } // do not filter it out if the static presence doesnt match - const mIsStatic = hasModifier(m, ModifierFlags.Static); - const currentElementIsStatic = !!(currentClassElementModifierFlags & ModifierFlags.Static); - if ((mIsStatic && !currentElementIsStatic) || - (!mIsStatic && currentElementIsStatic)) { + if (hasModifier(m, ModifierFlags.Static) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) { continue; } @@ -2006,24 +1990,10 @@ namespace ts.Completions { } } - const result: Symbol[] = []; - addPropertySymbols(baseSymbols, ModifierFlags.Private); - addPropertySymbols(implementingTypeSymbols, ModifierFlags.NonPublicAccessibilityModifier); - return result; - - function addPropertySymbols(properties: ReadonlyArray, inValidModifierFlags: ModifierFlags) { - for (const property of properties) { - if (isValidProperty(property, inValidModifierFlags)) { - result.push(property); - } - } - } - - function isValidProperty(propertySymbol: Symbol, inValidModifierFlags: ModifierFlags) { - return !existingMemberNames.get(propertySymbol.escapedName) && - propertySymbol.getDeclarations() && - !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & inValidModifierFlags); - } + return baseSymbols.filter(propertySymbol => + !existingMemberNames.has(propertySymbol.escapedName) && + !!propertySymbol.declarations && + !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private)); } /** diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index e742a7f1030..a151d776403 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1200,63 +1200,31 @@ namespace ts.FindAllReferences.Core { * distinction between structurally compatible implementations and explicit implementations, so we * must use the AST. * - * @param child A class or interface Symbol + * @param symbol A class or interface Symbol * @param parent Another class or interface Symbol * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results */ - function explicitlyInheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map, checker: TypeChecker): boolean { - const parentIsInterface = parent.getFlags() & SymbolFlags.Interface; - return searchHierarchy(child); - - function searchHierarchy(symbol: Symbol): boolean { - if (symbol === parent) { - return true; - } - - const key = getSymbolId(symbol) + "," + getSymbolId(parent); - const cached = cachedResults.get(key); - if (cached !== undefined) { - return cached; - } - - // Set the key so that we don't infinitely recurse - cachedResults.set(key, false); - - const inherits = forEach(symbol.getDeclarations(), declaration => { - if (isClassLike(declaration)) { - if (parentIsInterface) { - const interfaceReferences = getClassImplementsHeritageClauseElements(declaration); - if (interfaceReferences) { - for (const typeReference of interfaceReferences) { - if (searchTypeReference(typeReference)) { - return true; - } - } - } - } - return searchTypeReference(getClassExtendsHeritageClauseElement(declaration)); - } - else if (declaration.kind === SyntaxKind.InterfaceDeclaration) { - if (parentIsInterface) { - return forEach(getInterfaceBaseTypeNodes(declaration), searchTypeReference); - } - } - return false; - }); - - cachedResults.set(key, inherits); - return inherits; + function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: Map, checker: TypeChecker): boolean { + if (symbol === parent) { + return true; } - function searchTypeReference(typeReference: ExpressionWithTypeArguments): boolean { - if (typeReference) { + const key = getSymbolId(symbol) + "," + getSymbolId(parent); + const cached = cachedResults.get(key); + if (cached !== undefined) { + return cached; + } + + // Set the key so that we don't infinitely recurse + cachedResults.set(key, false); + + const inherits = symbol.declarations.some(declaration => + getAllSuperTypeNodes(declaration).some(typeReference => { const type = checker.getTypeAtLocation(typeReference); - if (type && type.symbol) { - return searchHierarchy(type.symbol); - } - } - return false; - } + return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); + })); + cachedResults.set(key, inherits); + return inherits; } function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] { @@ -1491,10 +1459,6 @@ namespace ts.FindAllReferences.Core { * The value of previousIterationSymbol is undefined when the function is first called. */ function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Push, previousIterationSymbolsCache: SymbolTable, checker: TypeChecker): void { - if (!symbol) { - return; - } - // If the current symbol is the same as the previous-iteration symbol, we can just return the symbol that has already been visited // This is particularly important for the following cases, so that we do not infinitely visit the same symbol. // For example: @@ -1506,27 +1470,16 @@ namespace ts.FindAllReferences.Core { // the function will add any found symbol of the property-name, then its sub-routine will call // getPropertySymbolsFromBaseTypes again to walk up any base types to prevent revisiting already // visited symbol, interface "C", the sub-routine will pass the current symbol as previousIterationSymbol. - if (previousIterationSymbolsCache.has(symbol.escapedName)) { + if (!symbol || previousIterationSymbolsCache.has(symbol.escapedName)) { return; } if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - forEach(symbol.getDeclarations(), declaration => { - if (isClassLike(declaration)) { - getPropertySymbolFromTypeReference(getClassExtendsHeritageClauseElement(declaration)); - forEach(getClassImplementsHeritageClauseElements(declaration), getPropertySymbolFromTypeReference); - } - else if (declaration.kind === SyntaxKind.InterfaceDeclaration) { - forEach(getInterfaceBaseTypeNodes(declaration), getPropertySymbolFromTypeReference); - } - }); - } - return; + for (const declaration of symbol.declarations) { + for (const typeReference of getAllSuperTypeNodes(declaration)) { + const type = checker.getTypeAtLocation(typeReference); + if (!type) continue; - function getPropertySymbolFromTypeReference(typeReference: ExpressionWithTypeArguments): void { - if (typeReference) { - const type = checker.getTypeAtLocation(typeReference); - if (type) { const propertySymbol = checker.getPropertyOfType(type, propertyName); if (propertySymbol) { result.push(...checker.getRootSymbols(propertySymbol)); diff --git a/src/services/services.ts b/src/services/services.ts index cd35e0375a3..590c65c4eae 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -576,7 +576,7 @@ namespace ts { */ function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): SymbolDisplayPart[] { let foundDocs = false; - return flatMap(getAllSuperTypeNodes(declaration), superTypeNode => { + return flatMap(declaration.parent ? getAllSuperTypeNodes(declaration.parent) : emptyArray, superTypeNode => { if (foundDocs) { return emptyArray; } @@ -594,21 +594,6 @@ namespace ts { }); } - /** - * Finds and returns the `TypeNode` for all super classes and implemented interfaces given a declaration. - * @param declaration The possibly-inherited declaration. - * @returns A filled array of `TypeNode`s containing all super classes and implemented interfaces if any exist, otherwise an empty array. - */ - function getAllSuperTypeNodes(declaration: Declaration): ReadonlyArray { - const container = declaration.parent; - if (!container || (!isClassDeclaration(container) && !isInterfaceDeclaration(container))) { - return emptyArray; - } - const extended = getClassExtendsHeritageClauseElement(container); - const types = extended ? [extended] : emptyArray; - return isClassLike(container) ? concatenate(types, getClassImplementsHeritageClauseElements(container)) : types; - } - class SourceFileObject extends NodeObject implements SourceFile { public kind: SyntaxKind.SourceFile; public _declarationBrand: any; diff --git a/tests/cases/fourslash/completionEntryForClassMembers2.ts b/tests/cases/fourslash/completionEntryForClassMembers2.ts index 539702196a3..9d2d158cd55 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers2.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers2.ts @@ -350,7 +350,7 @@ verifyClassElementLocations( value => value[0] !== "getValue"), ["extendsBAndImplementsI3WithSameNameMembersAndHasImplementedTheMember"]); -const invalidMembersOfB0AtInstanceSide = privateMembersOfBaseClassB0.concat(validStaticMembersOfBaseClassB0); +const invalidMembersOfB0AtInstanceSide = privateMembersOfBaseClassB0.concat(validStaticMembersOfBaseClassB0); const invalidMembersOfB0AtStaticSide = privateMembersOfBaseClassB0.concat(validInstanceMembersOfBaseClassB0); // members of B0 and members of I4 verifyClassElementLocations({ @@ -403,11 +403,10 @@ verifyClassElementLocations({ validMembers: membersOfI7, invalidMembers: noMembe "implementsIAndAlsoImplementsI7whichExtendsI" ]); -const invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 = invalidMembersOfB0AtInstanceSide - .concat(protectedPropertiesOfBaseClassB0); +const invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 = invalidMembersOfB0AtInstanceSide; // members of I5 extends B0 verifyClassElementLocations({ - validMembers: membersOfI5, + validMembers: membersOfI5.concat(protectedPropertiesOfBaseClassB0), invalidMembers: invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 }, [ "implementsI5ThatExtendsB0", @@ -415,7 +414,7 @@ verifyClassElementLocations({ // members of I6 extends B0 verifyClassElementLocations({ - validMembers: membersOfI6, + validMembers: membersOfI6.concat(protectedPropertiesOfBaseClassB0), invalidMembers: invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 }, [ "implementsI6ThatExtendsB0AndHasStaticMethodOfB0",