diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c159077f124..376bdc13c88 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -685,10 +685,6 @@ namespace ts { return type.flags & TypeFlags.Object ? (type).objectFlags : 0; } - function getCheckFlags(symbol: Symbol): CheckFlags { - return symbol.flags & SymbolFlags.Transient ? (symbol).checkFlags : 0; - } - function isGlobalSourceFile(node: Node) { return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node); } @@ -14047,25 +14043,6 @@ namespace ts { return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.PropertyDeclaration; } - function getDeclarationModifierFlagsFromSymbol(s: Symbol): ModifierFlags { - if (s.valueDeclaration) { - const flags = getCombinedModifierFlags(s.valueDeclaration); - return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; - } - if (getCheckFlags(s) & CheckFlags.Synthetic) { - const checkFlags = (s).checkFlags; - const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : - checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : - ModifierFlags.Protected; - const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; - return accessModifier | staticModifier; - } - if (s.flags & SymbolFlags.Prototype) { - return ModifierFlags.Public | ModifierFlags.Static; - } - return 0; - } - function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5725d04a9cf..a7384db9f52 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4235,6 +4235,29 @@ namespace ts { return options.watch && options.hasOwnProperty("watch"); } + export function getCheckFlags(symbol: Symbol): CheckFlags { + return symbol.flags & SymbolFlags.Transient ? (symbol).checkFlags : 0; + } + + export function getDeclarationModifierFlagsFromSymbol(s: Symbol): ModifierFlags { + if (s.valueDeclaration) { + const flags = getCombinedModifierFlags(s.valueDeclaration); + return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; + } + if (getCheckFlags(s) & CheckFlags.Synthetic) { + const checkFlags = (s).checkFlags; + const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : + checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : + ModifierFlags.Protected; + const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; + return accessModifier | staticModifier; + } + if (s.flags & SymbolFlags.Prototype) { + return ModifierFlags.Public | ModifierFlags.Static; + } + return 0; + } + export function levenshtein(s1: string, s2: string): number { let previous: number[] = new Array(s2.length + 1); let current: number[] = new Array(s2.length + 1); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 83d27c951be..70b67f3326e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3417,6 +3417,18 @@ namespace FourSlashInterface { export class VerifyNegatable { public not: VerifyNegatable; + public allowedClassElementKeywords = [ + "public", + "private", + "protected", + "static", + "abstract", + "readonly", + "get", + "set", + "constructor", + "async" + ]; constructor(protected state: FourSlash.TestState, private negative = false) { if (!negative) { @@ -3453,6 +3465,12 @@ namespace FourSlashInterface { this.state.verifyCompletionListIsEmpty(this.negative); } + public completionListContainsClassElementKeywords() { + for (const keyword of this.allowedClassElementKeywords) { + this.completionListContains(keyword, keyword, /*documentation*/ undefined, "keyword"); + } + } + public completionListIsGlobal(expected: boolean) { this.state.verifyCompletionListIsGlobal(expected); } diff --git a/src/services/completions.ts b/src/services/completions.ts index ce7e837e234..5bc2a268617 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -18,7 +18,7 @@ namespace ts.Completions { return undefined; } - const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, requestJsDocTagName, requestJsDocTag } = completionData; + const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords } = completionData; if (requestJsDocTagName) { // If the current position is a jsDoc tag name, only tag names should be provided for completion @@ -52,7 +52,7 @@ namespace ts.Completions { sortText: "0", }); } - else { + else if (!hasFilteredClassMemberKeywords) { return undefined; } } @@ -60,8 +60,11 @@ namespace ts.Completions { getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log); } + if (hasFilteredClassMemberKeywords) { + addRange(entries, classMemberKeywordCompletions); + } // Add keywords if this is not a member completion list - if (!isMemberCompletion && !requestJsDocTag && !requestJsDocTagName) { + else if (!isMemberCompletion && !requestJsDocTag && !requestJsDocTagName) { addRange(entries, keywordCompletions); } @@ -411,7 +414,7 @@ namespace ts.Completions { } if (requestJsDocTagName || requestJsDocTag) { - return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, requestJsDocTagName, requestJsDocTag }; + return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords: false }; } if (!insideJsDocTagExpression) { @@ -510,6 +513,7 @@ namespace ts.Completions { let isGlobalCompletion = false; let isMemberCompletion: boolean; let isNewIdentifierLocation: boolean; + let hasFilteredClassMemberKeywords = false; let symbols: Symbol[] = []; if (isRightOfDot) { @@ -547,7 +551,7 @@ namespace ts.Completions { log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), requestJsDocTagName, requestJsDocTag }; + return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords }; function getTypeScriptMemberSymbols(): void { // Right of dot member completion list @@ -604,6 +608,7 @@ namespace ts.Completions { function tryGetGlobalSymbols(): boolean { let objectLikeContainer: ObjectLiteralExpression | BindingPattern; let namedImportsOrExports: NamedImportsOrExports; + let classLikeContainer: ClassLikeDeclaration; let jsxContainer: JsxOpeningLikeElement; if (objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken)) { @@ -616,6 +621,12 @@ namespace ts.Completions { return tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports); } + if (classLikeContainer = tryGetClassLikeCompletionContainer(contextToken)) { + // cursor inside class declaration + getGetClassLikeCompletionSymbols(classLikeContainer); + return true; + } + if (jsxContainer = tryGetContainingJsxElement(contextToken)) { let attrsType: Type; if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) { @@ -913,6 +924,62 @@ namespace ts.Completions { return true; } + /** + * Aggregates relevant symbols for completion in class declaration + * Relevant symbols are stored in the captured 'symbols' variable. + */ + function getGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration) { + // We're looking up possible property names from parent type. + isMemberCompletion = true; + // Declaring new property/method/accessor + isNewIdentifierLocation = true; + // Has keywords for class elements + hasFilteredClassMemberKeywords = true; + + const baseTypeNode = getClassExtendsHeritageClauseElement(classLikeDeclaration); + const implementsTypeNodes = getClassImplementsHeritageClauseElements(classLikeDeclaration); + if (baseTypeNode || implementsTypeNodes) { + const classElement = contextToken.parent; + 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; + } + } + + // 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) ? + undefined : + 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) : + undefined, + implementedInterfaceTypePropertySymbols, + classLikeDeclaration.members, + classElementModifierFlags); + } + } + } + /** * Returns the immediate owning object literal or binding pattern of a context token, * on the condition that one exists and that the context implies completion should be given. @@ -953,6 +1020,49 @@ namespace ts.Completions { return undefined; } + function isFromClassElementDeclaration(node: Node) { + return isClassElement(node.parent) && isClassLike(node.parent.parent); + } + + /** + * 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: + case SyntaxKind.SemicolonToken: + // class c { method() { } | } + case SyntaxKind.CloseBraceToken: + if (isClassLike(location)) { + return location; + } + 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; + } + function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement { if (contextToken) { const parent = contextToken.parent; @@ -1081,7 +1191,7 @@ namespace ts.Completions { isFunction(containingNodeKind); case SyntaxKind.StaticKeyword: - return containingNodeKind === SyntaxKind.PropertyDeclaration; + return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(contextToken.parent.parent); case SyntaxKind.DotDotDotToken: return containingNodeKind === SyntaxKind.Parameter || @@ -1098,13 +1208,17 @@ namespace ts.Completions { containingNodeKind === SyntaxKind.ExportSpecifier || containingNodeKind === SyntaxKind.NamespaceImport; + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + if (isFromClassElementDeclaration(contextToken)) { + return false; + } + // falls through case SyntaxKind.ClassKeyword: case SyntaxKind.EnumKeyword: case SyntaxKind.InterfaceKeyword: case SyntaxKind.FunctionKeyword: case SyntaxKind.VarKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: case SyntaxKind.ImportKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: @@ -1113,6 +1227,13 @@ namespace ts.Completions { return true; } + // If the previous token is keyword correspoding to class member completion keyword + // there will be completion available here + if (isClassMemberCompletionKeywordText(contextToken.getText()) && + isFromClassElementDeclaration(contextToken)) { + return false; + } + // Previous token may have been a keyword that was converted to an identifier. switch (contextToken.getText()) { case "abstract": @@ -1159,7 +1280,7 @@ namespace ts.Completions { for (const element of namedImportsOrExports) { // If this is the current item we are editing right now, do not filter it out - if (element.getStart() <= position && position <= element.getEnd()) { + if (isCurrentlyEditingNode(element)) { continue; } @@ -1198,7 +1319,7 @@ namespace ts.Completions { } // If this is the current item we are editing right now, do not filter it out - if (m.getStart() <= position && position <= m.getEnd()) { + if (isCurrentlyEditingNode(m)) { continue; } @@ -1223,6 +1344,58 @@ namespace ts.Completions { return filter(contextualMemberSymbols, m => !existingMemberNames.get(m.name)); } + /** + * Filters out completion suggestions for class elements. + * + * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags + */ + function filterClassMembersList(baseSymbols: Symbol[], implementingTypeSymbols: Symbol[], existingMembers: ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { + const existingMemberNames = createMap(); + for (const m of existingMembers) { + // Ignore omitted expressions for missing members + if (m.kind !== SyntaxKind.PropertyDeclaration && + m.kind !== SyntaxKind.MethodDeclaration && + m.kind !== SyntaxKind.GetAccessor && + m.kind !== SyntaxKind.SetAccessor) { + continue; + } + + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(m)) { + continue; + } + + // Dont filter member even if the name matches if it is declared private in the list + if (hasModifier(m, ModifierFlags.Private)) { + continue; + } + + // 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)) { + continue; + } + + const existingName = getPropertyNameForPropertyNameNode(m.name); + if (existingName) { + existingMemberNames.set(existingName, true); + } + } + + return concatenate( + filter(baseSymbols, baseProperty => isValidProperty(baseProperty, ModifierFlags.Private)), + filter(implementingTypeSymbols, implementingProperty => isValidProperty(implementingProperty, ModifierFlags.NonPublicAccessibilityModifier)) + ); + + function isValidProperty(propertySymbol: Symbol, inValidModifierFlags: ModifierFlags) { + return !existingMemberNames.get(propertySymbol.name) && + propertySymbol.getDeclarations() && + !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & inValidModifierFlags); + } + } + /** * Filters out completion suggestions from 'symbols' according to existing JSX attributes. * @@ -1233,7 +1406,7 @@ namespace ts.Completions { const seenNames = createMap(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out - if (attr.getStart() <= position && position <= attr.getEnd()) { + if (isCurrentlyEditingNode(attr)) { continue; } @@ -1244,6 +1417,10 @@ namespace ts.Completions { return filter(symbols, a => !seenNames.get(a.name)); } + + function isCurrentlyEditingNode(node: Node): boolean { + return node.getStart() <= position && position <= node.getEnd(); + } } /** @@ -1306,6 +1483,29 @@ namespace ts.Completions { }); } + function isClassMemberCompletionKeyword(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.PublicKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.ConstructorKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + case SyntaxKind.AsyncKeyword: + return true; + } + } + + function isClassMemberCompletionKeywordText(text: string) { + return isClassMemberCompletionKeyword(stringToToken(text)); + } + + const classMemberKeywordCompletions = filter(keywordCompletions, entry => + isClassMemberCompletionKeywordText(entry.name)); + function isEqualityExpression(node: Node): node is BinaryExpression { return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind); } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 5bf3e37f28a..4268a52c419 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -200,27 +200,33 @@ namespace ts.SymbolDisplay { (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration // get the signature from the declaration and write it const functionDeclaration = location.parent; - const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); - if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { - signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); - } - else { - signature = allSignatures[0]; - } + // Use function declaration to write the signatures only if the symbol corresponding to this declaration + const locationIsSymbolDeclaration = findDeclaration(symbol, declaration => + declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); - if (functionDeclaration.kind === SyntaxKind.Constructor) { - // show (constructor) Type(...) signature - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); - } - else { - // (function/method) symbol(..signature) - addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && - !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); - } + if (locationIsSymbolDeclaration) { + const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); + if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { + signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); + } + else { + signature = allSignatures[0]; + } - addSignatureDisplayParts(signature, allSignatures); - hasAddedSymbolInfo = true; + if (functionDeclaration.kind === SyntaxKind.Constructor) { + // show (constructor) Type(...) signature + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + } + else { + // (function/method) symbol(..signature) + addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && + !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); + } + + addSignatureDisplayParts(signature, allSignatures); + hasAddedSymbolInfo = true; + } } } } diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts new file mode 100644 index 00000000000..527611b85bf --- /dev/null +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -0,0 +1,256 @@ +/// + +////abstract class B { +//// private privateMethod() { } +//// protected protectedMethod() { }; +//// static staticMethod() { } +//// abstract getValue(): number; +//// /*abstractClass*/ +////} +////class C extends B { +//// /*classThatIsEmptyAndExtendingAnotherClass*/ +////} +////class D extends B { +//// /*classThatHasAlreadyImplementedAnotherClassMethod*/ +//// getValue() { +//// return 10; +//// } +//// /*classThatHasAlreadyImplementedAnotherClassMethodAfterMethod*/ +////} +////class D1 extends B { +//// /*classThatHasDifferentMethodThanBase*/ +//// getValue1() { +//// return 10; +//// } +//// /*classThatHasDifferentMethodThanBaseAfterMethod*/ +////} +////class D2 extends B { +//// /*classThatHasAlreadyImplementedAnotherClassProtectedMethod*/ +//// protectedMethod() { +//// } +//// /*classThatHasDifferentMethodThanBaseAfterProtectedMethod*/ +////} +////class D3 extends D1 { +//// /*classThatExtendsClassExtendingAnotherClass*/ +////} +////class D4 extends D1 { +//// static /*classThatExtendsClassExtendingAnotherClassAndTypesStatic*/ +////} +////class D5 extends D2 { +//// /*classThatExtendsClassExtendingAnotherClassWithOverridingMember*/ +////} +////class D6 extends D2 { +//// static /*classThatExtendsClassExtendingAnotherClassWithOverridingMemberAndTypesStatic*/ +////} +////class E { +//// /*classThatDoesNotExtendAnotherClass*/ +////} +////class F extends B { +//// public /*classThatHasWrittenPublicKeyword*/ +////} +////class F2 extends B { +//// private /*classThatHasWrittenPrivateKeyword*/ +////} +////class G extends B { +//// static /*classElementContainingStatic*/ +////} +////class G2 extends B { +//// private static /*classElementContainingPrivateStatic*/ +////} +////class H extends B { +//// prop/*classThatStartedWritingIdentifier*/ +////} +//////Class for location verification +////class I extends B { +//// prop0: number +//// /*propDeclarationWithoutSemicolon*/ +//// prop: number; +//// /*propDeclarationWithSemicolon*/ +//// prop1 = 10; +//// /*propAssignmentWithSemicolon*/ +//// prop2 = 10 +//// /*propAssignmentWithoutSemicolon*/ +//// method(): number +//// /*methodSignatureWithoutSemicolon*/ +//// method2(): number; +//// /*methodSignatureWithSemicolon*/ +//// method3() { +//// /*InsideMethod*/ +//// } +//// /*methodImplementation*/ +//// get c() +//// /*accessorSignatureWithoutSemicolon*/ +//// set c() +//// { +//// } +//// /*accessorSignatureImplementation*/ +////} +////class J extends B { +//// get /*classThatHasWrittenGetKeyword*/ +////} +////class K extends B { +//// set /*classThatHasWrittenSetKeyword*/ +////} +////class J extends B { +//// get identi/*classThatStartedWritingIdentifierOfGetAccessor*/ +////} +////class K extends B { +//// set identi/*classThatStartedWritingIdentifierOfSetAccessor*/ +////} +////class L extends B { +//// public identi/*classThatStartedWritingIdentifierAfterModifier*/ +////} +////class L2 extends B { +//// private identi/*classThatStartedWritingIdentifierAfterPrivateModifier*/ +////} +////class M extends B { +//// static identi/*classThatStartedWritingIdentifierAfterStaticModifier*/ +////} +////class M extends B { +//// private static identi/*classThatStartedWritingIdentifierAfterPrivateStaticModifier*/ +////} +////class N extends B { +//// async /*classThatHasWrittenAsyncKeyword*/ +////} + +const allowedKeywordCount = verify.allowedClassElementKeywords.length; +type CompletionInfo = [string, string]; +type CompletionInfoVerifier = { validMembers: CompletionInfo[], invalidMembers: CompletionInfo[] }; + +function verifyClassElementLocations({ validMembers, invalidMembers }: CompletionInfoVerifier, classElementCompletionLocations: string[]) { + for (const marker of classElementCompletionLocations) { + goTo.marker(marker); + verifyCompletionInfo(validMembers, verify); + verifyCompletionInfo(invalidMembers, verify.not); + verify.completionListContainsClassElementKeywords(); + verify.completionListCount(allowedKeywordCount + validMembers.length); + } +} + +function verifyCompletionInfo(memberInfo: CompletionInfo[], verify: FourSlashInterface.verifyNegatable) { + for (const [symbol, text] of memberInfo) { + verify.completionListContains(symbol, text, /*documentation*/ undefined, "method"); + } +} + +const allMembersOfBase: CompletionInfo[] = [ + ["getValue", "(method) B.getValue(): number"], + ["protectedMethod", "(method) B.protectedMethod(): void"], + ["privateMethod", "(method) B.privateMethod(): void"], + ["staticMethod", "(method) B.staticMethod(): void"] +]; +const publicCompletionInfoOfD1: CompletionInfo[] = [ + ["getValue1", "(method) D1.getValue1(): number"] +]; +const publicCompletionInfoOfD2: CompletionInfo[] = [ + ["protectedMethod", "(method) D2.protectedMethod(): void"] +]; +function filterCompletionInfo(fn: (a: CompletionInfo) => boolean): CompletionInfoVerifier { + const validMembers: CompletionInfo[] = []; + const invalidMembers: CompletionInfo[] = []; + for (const member of allMembersOfBase) { + if (fn(member)) { + validMembers.push(member); + } + else { + invalidMembers.push(member); + } + } + return { validMembers, invalidMembers }; +} + + +const instanceMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue" || a === "protectedMethod"); +const staticMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "staticMethod"); +const instanceWithoutProtectedMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue"); +const instanceWithoutPublicMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "protectedMethod"); + +const instanceMemberInfoD1: CompletionInfoVerifier = { + validMembers: instanceMemberInfo.validMembers.concat(publicCompletionInfoOfD1), + invalidMembers: instanceMemberInfo.invalidMembers +}; +const instanceMemberInfoD2: CompletionInfoVerifier = { + validMembers: instanceWithoutProtectedMemberInfo.validMembers.concat(publicCompletionInfoOfD2), + invalidMembers: instanceWithoutProtectedMemberInfo.invalidMembers +}; +const staticMemberInfoDn: CompletionInfoVerifier = { + validMembers: staticMemberInfo.validMembers, + invalidMembers: staticMemberInfo.invalidMembers.concat(publicCompletionInfoOfD1, publicCompletionInfoOfD2) +}; + +// Not a class element declaration location +const nonClassElementMarkers = [ + "InsideMethod" +]; +for (const marker of nonClassElementMarkers) { + goTo.marker(marker); + verifyCompletionInfo(allMembersOfBase, verify.not); + verify.not.completionListIsEmpty(); +} + +// Only keywords allowed at this position since they dont extend the class or are private +const onlyClassElementKeywordLocations = [ + "abstractClass", + "classThatDoesNotExtendAnotherClass", + "classThatHasWrittenPrivateKeyword", + "classElementContainingPrivateStatic", + "classThatStartedWritingIdentifierAfterPrivateModifier", + "classThatStartedWritingIdentifierAfterPrivateStaticModifier" +]; +verifyClassElementLocations({ validMembers: [], invalidMembers: allMembersOfBase }, onlyClassElementKeywordLocations); + +// Instance base members and class member keywords allowed +const classInstanceElementLocations = [ + "classThatIsEmptyAndExtendingAnotherClass", + "classThatHasDifferentMethodThanBase", + "classThatHasDifferentMethodThanBaseAfterMethod", + "classThatHasWrittenPublicKeyword", + "classThatStartedWritingIdentifier", + "propDeclarationWithoutSemicolon", + "propDeclarationWithSemicolon", + "propAssignmentWithSemicolon", + "propAssignmentWithoutSemicolon", + "methodSignatureWithoutSemicolon", + "methodSignatureWithSemicolon", + "methodImplementation", + "accessorSignatureWithoutSemicolon", + "accessorSignatureImplementation", + "classThatHasWrittenGetKeyword", + "classThatHasWrittenSetKeyword", + "classThatStartedWritingIdentifierOfGetAccessor", + "classThatStartedWritingIdentifierOfSetAccessor", + "classThatStartedWritingIdentifierAfterModifier", + "classThatHasWrittenAsyncKeyword" +]; +verifyClassElementLocations(instanceMemberInfo, classInstanceElementLocations); + +// Static Base members and class member keywords allowed +const staticClassLocations = [ + "classElementContainingStatic", + "classThatStartedWritingIdentifierAfterStaticModifier" +]; +verifyClassElementLocations(staticMemberInfo, staticClassLocations); + +const classInstanceElementWithoutPublicMethodLocations = [ + "classThatHasAlreadyImplementedAnotherClassMethod", + "classThatHasAlreadyImplementedAnotherClassMethodAfterMethod", +]; +verifyClassElementLocations(instanceWithoutPublicMemberInfo, classInstanceElementWithoutPublicMethodLocations); + +const classInstanceElementWithoutProtectedMethodLocations = [ + "classThatHasAlreadyImplementedAnotherClassProtectedMethod", + "classThatHasDifferentMethodThanBaseAfterProtectedMethod", +]; +verifyClassElementLocations(instanceWithoutProtectedMemberInfo, classInstanceElementWithoutProtectedMethodLocations); + +// instance memebers in D1 and base class are shown +verifyClassElementLocations(instanceMemberInfoD1, ["classThatExtendsClassExtendingAnotherClass"]); + +// instance memebers in D2 and base class are shown +verifyClassElementLocations(instanceMemberInfoD2, ["classThatExtendsClassExtendingAnotherClassWithOverridingMember"]); + +// static base members and class member keywords allowed +verifyClassElementLocations(staticMemberInfoDn, [ + "classThatExtendsClassExtendingAnotherClassAndTypesStatic", + "classThatExtendsClassExtendingAnotherClassWithOverridingMemberAndTypesStatic" +]); \ No newline at end of file diff --git a/tests/cases/fourslash/completionEntryForClassMembers2.ts b/tests/cases/fourslash/completionEntryForClassMembers2.ts new file mode 100644 index 00000000000..539702196a3 --- /dev/null +++ b/tests/cases/fourslash/completionEntryForClassMembers2.ts @@ -0,0 +1,456 @@ +/// + +////interface I { +//// methodOfInterface(): number; +////} +////interface I2 { +//// methodOfInterface2(): number; +////} +////interface I3 { +//// getValue(): string; +//// method(): string; +////} +////interface I4 { +//// staticMethod(): void; +//// method(): string; +////} +////class B0 { +//// private privateMethod() { } +//// protected protectedMethod() { } +//// static staticMethod() { } +//// getValue(): string | boolean { return "hello"; } +//// private privateMethod1() { } +//// protected protectedMethod1() { } +//// static staticMethod1() { } +//// getValue1(): string | boolean { return "hello"; } +////} +////interface I5 extends B0 { +//// methodOfInterface5(): number; +////} +////interface I6 extends B0 { +//// methodOfInterface6(): number; +//// staticMethod(): void; +////} +////interface I7 extends I { +//// methodOfInterface7(): number; +////} +////class B { +//// private privateMethod() { } +//// protected protectedMethod() { } +//// static staticMethod() { } +//// getValue(): string | boolean { return "hello"; } +////} +////class C0 implements I, I2 { +//// /*implementsIAndI2*/ +////} +////class C00 implements I, I2 { +//// static /*implementsIAndI2AndWritingStatic*/ +////} +////class C001 implements I, I2 { +//// methodOfInterface/*implementsIAndI2AndWritingMethodNameOfI*/ +////} +////class C extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2*/ +////} +////class C1 extends B implements I, I2 { +//// static /*extendsBAndImplementsIAndI2AndWritingStatic*/ +////} +////class D extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2WithMethodFromB*/ +//// protected protectedMethod() { +//// return "protected"; +//// } +////} +////class E extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2WithMethodFromI*/ +//// methodOfInterface() { +//// return 1; +//// } +////} +////class F extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2WithMethodFromBAndI*/ +//// protected protectedMethod() { +//// return "protected" +//// } +//// methodOfInterface() { +//// return 1; +//// } +////} +////class F2 extends B implements I, I2 { +//// protected protectedMethod() { +//// return "protected" +//// } +//// methodOfInterface() { +//// return 1; +//// } +//// static /*extendsBAndImplementsIAndI2WithMethodFromBAndIAndTypesStatic*/ +////} +////class G extends B implements I3 { +//// /*extendsBAndImplementsI3WithSameNameMembers*/ +////} +////class H extends B implements I3 { +//// /*extendsBAndImplementsI3WithSameNameMembersAndHasImplementedTheMember*/ +//// getValue() { +//// return "hello"; +//// } +////} +////class J extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethod*/ +////} +////class L extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethod*/ +//// staticMethod2() { +//// return "hello"; +//// } +////} +////class K extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethod*/ +//// staticMethod() { +//// return "hello"; +//// } +////} +////class M extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStatic*/ +//// static staticMethod() { +//// return "hello"; +//// } +////} +////class N extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBoth*/ +//// staticMethod() { +//// return "hello"; +//// } +//// static staticMethod() { +//// return "hello"; +//// } +////} +////class J1 extends B0 implements I4 { +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodWritingStatic*/ +////} +////class L1 extends B0 implements I4 { +//// staticMethod2() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethodWritingStatic*/ +////} +////class K1 extends B0 implements I4 { +//// staticMethod() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodWritingStatic*/ +////} +////class M1 extends B0 implements I4 { +//// static staticMethod() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStaticWritingStatic*/ +////} +////class N1 extends B0 implements I4 { +//// staticMethod() { +//// return "hello"; +//// } +//// static staticMethod() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBothWritingStatic*/ +////} +////class O implements I7 { +//// /*implementsI7whichExtendsI*/ +////} +////class P implements I7, I { +//// /*implementsI7whichExtendsIAndAlsoImplementsI*/ +////} +////class Q implements I, I7 { +//// /*implementsIAndAlsoImplementsI7whichExtendsI*/ +////} +////class R implements I5 { +//// /*implementsI5ThatExtendsB0*/ +////} +////class S implements I6 { +//// /*implementsI6ThatExtendsB0AndHasStaticMethodOfB0*/ +////} +////class T extends B0 implements I5 { +//// /*extendsB0AndImplementsI5ThatExtendsB0*/ +////} +////class U extends B0 implements I6 { +//// /*extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0*/ +////} +////class R1 implements I5 { +//// static /*implementsI5ThatExtendsB0TypesStatic*/ +////} +////class S1 implements I6 { +//// static /*implementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic*/ +////} +////class T1 extends B0 implements I5 { +//// static /*extendsB0AndImplementsI5ThatExtendsB0TypesStatic*/ +////} +////class U1 extends B0 implements I6 { +//// static /*extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic*/ +////} + +const allowedKeywordCount = verify.allowedClassElementKeywords.length; +type CompletionInfo = [string, string]; +type CompletionInfoVerifier = { validMembers: CompletionInfo[], invalidMembers: CompletionInfo[] }; + +function verifyClassElementLocations({ validMembers, invalidMembers }: CompletionInfoVerifier, classElementCompletionLocations: string[]) { + for (const marker of classElementCompletionLocations) { + goTo.marker(marker); + verifyCompletionInfo(validMembers, verify); + verifyCompletionInfo(invalidMembers, verify.not); + verify.completionListContainsClassElementKeywords(); + verify.completionListCount(allowedKeywordCount + validMembers.length); + } +} + +function verifyCompletionInfo(memberInfo: CompletionInfo[], verify: FourSlashInterface.verifyNegatable) { + for (const [symbol, text] of memberInfo) { + verify.completionListContains(symbol, text, /*documentation*/ undefined, "method"); + } +} + +const validInstanceMembersOfBaseClassB: CompletionInfo[] = [ + ["getValue", "(method) B.getValue(): string | boolean"], + ["protectedMethod", "(method) B.protectedMethod(): void"], +]; +const validStaticMembersOfBaseClassB: CompletionInfo[] = [ + ["staticMethod", "(method) B.staticMethod(): void"] +]; +const privateMembersOfBaseClassB: CompletionInfo[] = [ + ["privateMethod", "(method) B.privateMethod(): void"], +]; +const protectedPropertiesOfBaseClassB0: CompletionInfo[] = [ + ["protectedMethod", "(method) B0.protectedMethod(): void"], + ["protectedMethod1", "(method) B0.protectedMethod1(): void"], +]; +const publicPropertiesOfBaseClassB0: CompletionInfo[] = [ + ["getValue", "(method) B0.getValue(): string | boolean"], + ["getValue1", "(method) B0.getValue1(): string | boolean"], +]; +const validInstanceMembersOfBaseClassB0: CompletionInfo[] = protectedPropertiesOfBaseClassB0.concat(publicPropertiesOfBaseClassB0); +const validStaticMembersOfBaseClassB0: CompletionInfo[] = [ + ["staticMethod", "(method) B0.staticMethod(): void"], + ["staticMethod1", "(method) B0.staticMethod1(): void"] +]; +const privateMembersOfBaseClassB0: CompletionInfo[] = [ + ["privateMethod", "(method) B0.privateMethod(): void"], + ["privateMethod1", "(method) B0.privateMethod1(): void"], +]; +const membersOfI: CompletionInfo[] = [ + ["methodOfInterface", "(method) I.methodOfInterface(): number"], +]; +const membersOfI2: CompletionInfo[] = [ + ["methodOfInterface2", "(method) I2.methodOfInterface2(): number"], +]; +const membersOfI3: CompletionInfo[] = [ + ["getValue", "(method) I3.getValue(): string"], + ["method", "(method) I3.method(): string"], +]; +const membersOfI4: CompletionInfo[] = [ + ["staticMethod", "(method) I4.staticMethod(): void"], + ["method", "(method) I4.method(): string"], +]; +const membersOfI5: CompletionInfo[] = publicPropertiesOfBaseClassB0.concat([ + ["methodOfInterface5", "(method) I5.methodOfInterface5(): number"] +]); +const membersOfI6: CompletionInfo[] = publicPropertiesOfBaseClassB0.concat([ + ["staticMethod", "(method) I6.staticMethod(): void"], + ["methodOfInterface6", "(method) I6.methodOfInterface6(): number"] +]); +const membersOfI7: CompletionInfo[] = membersOfI.concat([ + ["methodOfInterface7", "(method) I7.methodOfInterface7(): number"] +]); + +function getCompletionInfoVerifier( + validMembers: CompletionInfo[], + invalidMembers: CompletionInfo[], + arrayToDistribute: CompletionInfo[], + isValidDistributionCriteria: (v: CompletionInfo) => boolean): CompletionInfoVerifier { + if (arrayToDistribute) { + validMembers = validMembers.concat(arrayToDistribute.filter(isValidDistributionCriteria)); + invalidMembers = invalidMembers.concat(arrayToDistribute.filter(v => !isValidDistributionCriteria(v))); + } + return { + validMembers, + invalidMembers + } +} + +const noMembers: CompletionInfo[] = []; +const membersOfIAndI2 = membersOfI.concat(membersOfI2); +const invalidMembersOfBAtInstanceLocation = privateMembersOfBaseClassB.concat(validStaticMembersOfBaseClassB); + +// members of I and I2 +verifyClassElementLocations({ validMembers: membersOfIAndI2, invalidMembers: noMembers }, [ + "implementsIAndI2", + "implementsIAndI2AndWritingMethodNameOfI" +]); + +// Static location so no members of I and I2 +verifyClassElementLocations({ validMembers: noMembers, invalidMembers: membersOfIAndI2 }, + ["implementsIAndI2AndWritingStatic"]); + +const allInstanceBAndIAndI2 = membersOfIAndI2.concat(validInstanceMembersOfBaseClassB); +// members of instance B, I and I2 +verifyClassElementLocations({ + validMembers: allInstanceBAndIAndI2, + invalidMembers: invalidMembersOfBAtInstanceLocation +}, ["extendsBAndImplementsIAndI2"]); + +// static location so only static members of B and no members of instance B, I and I2 +verifyClassElementLocations({ + validMembers: validStaticMembersOfBaseClassB, + invalidMembers: privateMembersOfBaseClassB.concat(allInstanceBAndIAndI2) +}, [ + "extendsBAndImplementsIAndI2AndWritingStatic", + "extendsBAndImplementsIAndI2WithMethodFromBAndIAndTypesStatic" + ]); + +// instance members of B without protectedMethod, I and I2 +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ membersOfIAndI2, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ validInstanceMembersOfBaseClassB, + value => value[0] !== "protectedMethod"), + ["extendsBAndImplementsIAndI2WithMethodFromB"]); + +// instance members of B, members of T without methodOfInterface and I2 +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ membersOfI2.concat(validInstanceMembersOfBaseClassB), + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI, + value => value[0] !== "methodOfInterface"), + ["extendsBAndImplementsIAndI2WithMethodFromI"]); + +// instance members of B without protectedMethod, members of T without methodOfInterface and I2 +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ membersOfI2, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI.concat(validInstanceMembersOfBaseClassB), + value => value[0] !== "methodOfInterface" && value[0] !== "protectedMethod"), + ["extendsBAndImplementsIAndI2WithMethodFromBAndI"]); + +// members of B and members of I3 that are not same as name of method in B +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ validInstanceMembersOfBaseClassB, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI3, + value => value[0] !== "getValue"), + ["extendsBAndImplementsI3WithSameNameMembers"]); + +// members of B (without getValue since its implemented) and members of I3 that are not same as name of method in B +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ noMembers, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI3.concat(validInstanceMembersOfBaseClassB), + value => value[0] !== "getValue"), + ["extendsBAndImplementsI3WithSameNameMembersAndHasImplementedTheMember"]); + +const invalidMembersOfB0AtInstanceSide = privateMembersOfBaseClassB0.concat(validStaticMembersOfBaseClassB0); +const invalidMembersOfB0AtStaticSide = privateMembersOfBaseClassB0.concat(validInstanceMembersOfBaseClassB0); +// members of B0 and members of I4 +verifyClassElementLocations({ + validMembers: validInstanceMembersOfBaseClassB0.concat(membersOfI4), + invalidMembers: invalidMembersOfB0AtInstanceSide +}, [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethod", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethod", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStatic" + ]); + +// members of B0 and members of I4 that are not staticMethod +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ validInstanceMembersOfBaseClassB0, + /*invalidMembers*/ invalidMembersOfB0AtInstanceSide, + /*arrayToDistribute*/ membersOfI4, + value => value[0] !== "staticMethod" + ), [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethod", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBoth" + ]); + +// static members of B0 +verifyClassElementLocations({ + validMembers: validStaticMembersOfBaseClassB0, + invalidMembers: invalidMembersOfB0AtStaticSide.concat(membersOfI4) +}, [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodWritingStatic", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethodWritingStatic", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodWritingStatic" + ]); + +// static members of B0 without staticMethod +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ noMembers, + /*invalidMembers*/ invalidMembersOfB0AtStaticSide.concat(membersOfI4), + /*arrayToDistribute*/ validStaticMembersOfBaseClassB0, + value => value[0] !== "staticMethod" + ), [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStaticWritingStatic", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBothWritingStatic" + ]); + +// members of I7 extends I +verifyClassElementLocations({ validMembers: membersOfI7, invalidMembers: noMembers }, [ + "implementsI7whichExtendsI", + "implementsI7whichExtendsIAndAlsoImplementsI", + "implementsIAndAlsoImplementsI7whichExtendsI" +]); + +const invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 = invalidMembersOfB0AtInstanceSide + .concat(protectedPropertiesOfBaseClassB0); +// members of I5 extends B0 +verifyClassElementLocations({ + validMembers: membersOfI5, + invalidMembers: invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 +}, [ + "implementsI5ThatExtendsB0", + ]); + +// members of I6 extends B0 +verifyClassElementLocations({ + validMembers: membersOfI6, + invalidMembers: invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 +}, [ + "implementsI6ThatExtendsB0AndHasStaticMethodOfB0", + ]); + +// members of B0 and I5 that extends B0 +verifyClassElementLocations({ + validMembers: membersOfI5.concat(protectedPropertiesOfBaseClassB0), + invalidMembers: invalidMembersOfB0AtInstanceSide +}, [ + "extendsB0AndImplementsI5ThatExtendsB0" + ]); + +// members of B0 and I6 that extends B0 +verifyClassElementLocations({ + validMembers: membersOfI6.concat(protectedPropertiesOfBaseClassB0), + invalidMembers: invalidMembersOfB0AtInstanceSide +}, [ + "extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0" + ]); + +// nothing on static side as these do not extend any other class +verifyClassElementLocations({ + validMembers: [], + invalidMembers: membersOfI5.concat(membersOfI6, invalidMembersOfB0AtStaticSide) +}, [ + "implementsI5ThatExtendsB0TypesStatic", + "implementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic" + ]); + +// statics of base B but nothing from instance side +verifyClassElementLocations({ + validMembers: validStaticMembersOfBaseClassB0, + invalidMembers: membersOfI5.concat(membersOfI6, invalidMembersOfB0AtStaticSide) +}, [ + "extendsB0AndImplementsI5ThatExtendsB0TypesStatic", + "extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic" + ]); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListBuilderLocations_properties.ts b/tests/cases/fourslash/completionListBuilderLocations_properties.ts index 806d8c1de4f..2cdc3b7e7ba 100644 --- a/tests/cases/fourslash/completionListBuilderLocations_properties.ts +++ b/tests/cases/fourslash/completionListBuilderLocations_properties.ts @@ -10,4 +10,4 @@ //// public static a/*property2*/ ////} -goTo.eachMarker(() => verify.completionListIsEmpty()); +goTo.eachMarker(() => verify.completionListContainsClassElementKeywords()); diff --git a/tests/cases/fourslash/completionListInNamedClassExpression.ts b/tests/cases/fourslash/completionListInNamedClassExpression.ts index cf0d170f6a5..8ca6806ce7f 100644 --- a/tests/cases/fourslash/completionListInNamedClassExpression.ts +++ b/tests/cases/fourslash/completionListInNamedClassExpression.ts @@ -1,4 +1,4 @@ -/// +/// //// var x = class myClass { //// getClassName (){ @@ -11,4 +11,4 @@ goTo.marker("0"); verify.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); goTo.marker("1"); -verify.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); \ No newline at end of file +verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts b/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts index e1274e6f592..fd347d58037 100644 --- a/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts +++ b/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts @@ -1,4 +1,4 @@ -/// +/// //// class myClass { /*0*/ } //// /*1*/ @@ -16,7 +16,7 @@ //// } goTo.marker("0"); -verify.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); +verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); goTo.marker("1"); @@ -28,7 +28,7 @@ verify.completionListContains("myClass", "(local class) myClass", /*documentatio verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); goTo.marker("3"); -verify.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); +verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); goTo.marker("4"); @@ -36,5 +36,5 @@ verify.completionListContains("myClass", "class myClass", /*documentation*/ unde verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); goTo.marker("5"); -verify.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); +verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); diff --git a/tests/cases/fourslash/completionListIsGlobalCompletion.ts b/tests/cases/fourslash/completionListIsGlobalCompletion.ts index a1fadc6bb12..121ab940d65 100644 --- a/tests/cases/fourslash/completionListIsGlobalCompletion.ts +++ b/tests/cases/fourslash/completionListIsGlobalCompletion.ts @@ -53,7 +53,7 @@ verify.completionListIsGlobal(false); goTo.marker("9"); verify.completionListIsGlobal(false); goTo.marker("10"); -verify.completionListIsGlobal(true); +verify.completionListIsGlobal(false); goTo.marker("11"); verify.completionListIsGlobal(true); goTo.marker("12"); diff --git a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts index 51e4013e53a..916ad10fe9c 100644 --- a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts +++ b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts @@ -225,27 +225,28 @@ //// ////var shwvar = 1; -function goToMarkAndGeneralVerify(marker: string) +function goToMarkAndGeneralVerify(marker: string, isClassScope?: boolean) { goTo.marker(marker); - verify.completionListContains('mod1var', 'var mod1var: number'); - verify.completionListContains('mod1fn', 'function mod1fn(): void'); - verify.completionListContains('mod1cls', 'class mod1cls'); - verify.completionListContains('mod1int', 'interface mod1int'); - verify.completionListContains('mod1mod', 'namespace mod1mod'); - verify.completionListContains('mod1evar', 'var mod1.mod1evar: number'); - verify.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); - verify.completionListContains('mod1ecls', 'class mod1.mod1ecls'); - verify.completionListContains('mod1eint', 'interface mod1.mod1eint'); - verify.completionListContains('mod1emod', 'namespace mod1.mod1emod'); - verify.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); - verify.completionListContains('mod2', 'namespace mod2'); - verify.completionListContains('mod3', 'namespace mod3'); - verify.completionListContains('shwvar', 'var shwvar: number'); - verify.completionListContains('shwfn', 'function shwfn(): void'); - verify.completionListContains('shwcls', 'class shwcls'); - verify.completionListContains('shwint', 'interface shwint'); + const verifyModule = isClassScope ? verify.not : verify; + verifyModule.completionListContains('mod1var', 'var mod1var: number'); + verifyModule.completionListContains('mod1fn', 'function mod1fn(): void'); + verifyModule.completionListContains('mod1cls', 'class mod1cls'); + verifyModule.completionListContains('mod1int', 'interface mod1int'); + verifyModule.completionListContains('mod1mod', 'namespace mod1mod'); + verifyModule.completionListContains('mod1evar', 'var mod1.mod1evar: number'); + verifyModule.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); + verifyModule.completionListContains('mod1ecls', 'class mod1.mod1ecls'); + verifyModule.completionListContains('mod1eint', 'interface mod1.mod1eint'); + verifyModule.completionListContains('mod1emod', 'namespace mod1.mod1emod'); + verifyModule.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); + verifyModule.completionListContains('mod2', 'namespace mod2'); + verifyModule.completionListContains('mod3', 'namespace mod3'); + verifyModule.completionListContains('shwvar', 'var shwvar: number'); + verifyModule.completionListContains('shwfn', 'function shwfn(): void'); + verifyModule.completionListContains('shwcls', 'class shwcls'); + verifyModule.completionListContains('shwint', 'interface shwint'); verify.not.completionListContains('mod2var'); verify.not.completionListContains('mod2fn'); @@ -280,7 +281,7 @@ verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); // from class in mod1 -goToMarkAndGeneralVerify('class'); +goToMarkAndGeneralVerify('class', /*isClassScope*/ true); //verify.not.completionListContains('ceFunc'); //verify.not.completionListContains('ceVar'); @@ -306,7 +307,7 @@ verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); // from exported class in mod1 -goToMarkAndGeneralVerify('exportedClass'); +goToMarkAndGeneralVerify('exportedClass', /*isClassScope*/ true); //verify.not.completionListContains('ceFunc'); //verify.not.completionListContains('ceVar'); diff --git a/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts b/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts index 865887661e9..340d0bd3191 100644 --- a/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts +++ b/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts @@ -231,6 +231,39 @@ //// x: /*objectLiteral*/ ////} +goTo.marker('extendedClass'); + +verify.not.completionListContains('mod1'); +verify.not.completionListContains('mod2'); +verify.not.completionListContains('mod3'); +verify.not.completionListContains('shwvar', 'var shwvar: number'); +verify.not.completionListContains('shwfn', 'function shwfn(): void'); +verify.not.completionListContains('shwcls', 'class shwcls'); +verify.not.completionListContains('shwint', 'interface shwint'); + +verify.not.completionListContains('mod2var'); +verify.not.completionListContains('mod2fn'); +verify.not.completionListContains('mod2cls'); +verify.not.completionListContains('mod2int'); +verify.not.completionListContains('mod2mod'); +verify.not.completionListContains('mod2evar'); +verify.not.completionListContains('mod2efn'); +verify.not.completionListContains('mod2ecls'); +verify.not.completionListContains('mod2eint'); +verify.not.completionListContains('mod2emod'); +verify.not.completionListContains('sfvar'); +verify.not.completionListContains('sffn'); +verify.not.completionListContains('scvar'); +verify.not.completionListContains('scfn'); +verify.completionListContains('scpfn'); +verify.completionListContains('scpvar'); +verify.not.completionListContains('scsvar'); +verify.not.completionListContains('scsfn'); +verify.not.completionListContains('sivar'); +verify.not.completionListContains('sifn'); +verify.not.completionListContains('mod1exvar'); +verify.not.completionListContains('mod2eexvar'); + function goToMarkerAndVerify(marker: string) { goTo.marker(marker); @@ -267,8 +300,6 @@ function goToMarkerAndVerify(marker: string) verify.not.completionListContains('mod2eexvar'); } -goToMarkerAndVerify('extendedClass'); - goToMarkerAndVerify('objectLiteral'); goTo.marker('localVar'); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 374dfd33c7d..31d8b8b4ef2 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -133,11 +133,13 @@ declare namespace FourSlashInterface { class verifyNegatable { private negative; not: verifyNegatable; + allowedClassElementKeywords: string[]; constructor(negative?: boolean); completionListCount(expectedCount: number): void; completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number): void; completionListItemsCountIsGreaterThan(count: number): void; completionListIsEmpty(): void; + completionListContainsClassElementKeywords(): void; completionListAllowsNewIdentifier(): void; signatureHelpPresent(): void; errorExistsBetweenMarkers(startMarker: string, endMarker: string): void; diff --git a/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts b/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts index 3e453663f63..448c2627531 100644 --- a/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts +++ b/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts @@ -26,7 +26,7 @@ const methods = ranges.get("method"); const [m0, m1, m2] = methods; verify.referenceGroups(m0, [{ definition: "(method) Base.method(a?: T, b?: U): this", ranges: methods }]); verify.referenceGroups(m1, [ - { definition: "(method) Base.method(): void", ranges: [m0] }, + { definition: "(method) Base.method(a?: T, b?: U): this", ranges: [m0] }, { definition: "(method) MyClass.method(): void", ranges: [m1, m2] } ]); verify.referenceGroups(m2, [