Skip to content

Commit 588c4ec

Browse files
committed
Filter out existing members of the class from the completion list
1 parent a8ad40f commit 588c4ec

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

src/services/completions.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -927,8 +927,6 @@ namespace ts.Completions {
927927
/**
928928
* Aggregates relevant symbols for completion in class declaration
929929
* Relevant symbols are stored in the captured 'symbols' variable.
930-
*
931-
* @returns true if 'symbols' was successfully populated; false otherwise.
932930
*/
933931
function getGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration) {
934932
// We're looking up possible property names from parent type.
@@ -961,9 +959,8 @@ namespace ts.Completions {
961959
typeChecker.getTypeOfSymbolAtLocation(baseType.symbol, classLikeDeclaration) :
962960
baseType;
963961

964-
// List of property symbols of base type that are not private
965-
symbols = filter(typeChecker.getPropertiesOfType(typeToGetPropertiesFrom),
966-
baseProperty => baseProperty.getDeclarations() && !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private));
962+
// List of property symbols of base type that are not private and already implemented
963+
symbols = filterClassMembersList(typeChecker.getPropertiesOfType(typeToGetPropertiesFrom), classLikeDeclaration.members, classElementModifierFlags);
967964
}
968965
}
969966
}
@@ -1332,6 +1329,52 @@ namespace ts.Completions {
13321329
return filter(contextualMemberSymbols, m => !existingMemberNames.get(m.name));
13331330
}
13341331

1332+
/**
1333+
* Filters out completion suggestions for class elements.
1334+
*
1335+
* @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags
1336+
*/
1337+
function filterClassMembersList(baseSymbols: Symbol[], existingMembers: ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] {
1338+
const existingMemberNames = createMap<boolean>();
1339+
for (const m of existingMembers) {
1340+
// Ignore omitted expressions for missing members
1341+
if (m.kind !== SyntaxKind.PropertyDeclaration &&
1342+
m.kind !== SyntaxKind.MethodDeclaration &&
1343+
m.kind !== SyntaxKind.GetAccessor &&
1344+
m.kind !== SyntaxKind.SetAccessor) {
1345+
continue;
1346+
}
1347+
1348+
// If this is the current item we are editing right now, do not filter it out
1349+
if (isCurrentlyEditingNode(m)) {
1350+
continue;
1351+
}
1352+
1353+
// Dont filter member even if the name matches if it is declared private in the list
1354+
if (hasModifier(m, ModifierFlags.Private)) {
1355+
continue;
1356+
}
1357+
1358+
// do not filter it out if the static presence doesnt match
1359+
const mIsStatic = hasModifier(m, ModifierFlags.Static);
1360+
const currentElementIsStatic = !!(currentClassElementModifierFlags & ModifierFlags.Static);
1361+
if ((mIsStatic && currentElementIsStatic) ||
1362+
(!mIsStatic && currentElementIsStatic)) {
1363+
continue;
1364+
}
1365+
1366+
const existingName = getPropertyNameForPropertyNameNode(m.name);
1367+
if (existingName) {
1368+
existingMemberNames.set(existingName, true);
1369+
}
1370+
}
1371+
1372+
return filter(baseSymbols, baseProperty =>
1373+
!existingMemberNames.get(baseProperty.name) &&
1374+
baseProperty.getDeclarations() &&
1375+
!(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private));
1376+
}
1377+
13351378
/**
13361379
* Filters out completion suggestions from 'symbols' according to existing JSX attributes.
13371380
*

tests/cases/fourslash/completionEntryForClassMembers.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@
1717
//// }
1818
//// /*classThatHasAlreadyImplementedAnotherClassMethodAfterMethod*/
1919
////}
20+
////class D1 extends B {
21+
//// /*classThatHasDifferentMethodThanBase*/
22+
//// getValue1() {
23+
//// return 10;
24+
//// }
25+
//// /*classThatHasDifferentMethodThanBaseAfterMethod*/
26+
////}
27+
////class D2 extends B {
28+
//// /*classThatHasAlreadyImplementedAnotherClassProtectedMethod*/
29+
//// protectedMethod() {
30+
//// }
31+
//// /*classThatHasDifferentMethodThanBaseAfterProtectedMethod*/
32+
////}
2033
////class E {
2134
//// /*classThatDoesNotExtendAnotherClass*/
2235
////}
@@ -131,6 +144,8 @@ function filterCompletionInfo(fn: (a: CompletionInfo) => boolean): CompletionInf
131144

132145
const instanceMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue" || a === "protectedMethod");
133146
const staticMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "staticMethod");
147+
const instanceWithoutProtectedMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue");
148+
const instanceWithoutPublicMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "protectedMethod");
134149

135150
// Not a class element declaration location
136151
const nonClassElementMarkers = [
@@ -156,8 +171,8 @@ verifyClassElementLocations({ validMembers: [], invalidMembers: allMembersOfBase
156171
// Instance base members and class member keywords allowed
157172
const classInstanceElementLocations = [
158173
"classThatIsEmptyAndExtendingAnotherClass",
159-
"classThatHasAlreadyImplementedAnotherClassMethod",
160-
"classThatHasAlreadyImplementedAnotherClassMethodAfterMethod",
174+
"classThatHasDifferentMethodThanBase",
175+
"classThatHasDifferentMethodThanBaseAfterMethod",
161176
"classThatHasWrittenPublicKeyword",
162177
"classThatStartedWritingIdentifier",
163178
"propDeclarationWithoutSemicolon",
@@ -183,4 +198,16 @@ const staticClassLocations = [
183198
"classElementContainingStatic",
184199
"classThatStartedWritingIdentifierAfterStaticModifier"
185200
];
186-
verifyClassElementLocations(staticMemberInfo, staticClassLocations);
201+
verifyClassElementLocations(staticMemberInfo, staticClassLocations);
202+
203+
const classInstanceElementWithoutPublicMethodLocations = [
204+
"classThatHasAlreadyImplementedAnotherClassMethod",
205+
"classThatHasAlreadyImplementedAnotherClassMethodAfterMethod",
206+
];
207+
verifyClassElementLocations(instanceWithoutPublicMemberInfo, classInstanceElementWithoutPublicMethodLocations);
208+
209+
const classInstanceElementWithoutProtectedMethodLocations = [
210+
"classThatHasAlreadyImplementedAnotherClassProtectedMethod",
211+
"classThatHasDifferentMethodThanBaseAfterProtectedMethod",
212+
];
213+
verifyClassElementLocations(instanceWithoutProtectedMemberInfo, classInstanceElementWithoutProtectedMethodLocations);

0 commit comments

Comments
 (0)