Completion list for a class extending another class should contain members from base class

Handles #7158
This commit is contained in:
Sheetal Nandi 2017-04-27 15:46:07 -07:00
parent d483df94ef
commit b3d793608d
9 changed files with 313 additions and 57 deletions

View file

@ -679,10 +679,6 @@ namespace ts {
return type.flags & TypeFlags.Object ? (<ObjectType>type).objectFlags : 0;
}
function getCheckFlags(symbol: Symbol): CheckFlags {
return symbol.flags & SymbolFlags.Transient ? (<TransientSymbol>symbol).checkFlags : 0;
}
function isGlobalSourceFile(node: Node) {
return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>node);
}
@ -13928,25 +13924,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 = (<TransientSymbol>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;
}

View file

@ -4200,6 +4200,29 @@ namespace ts {
// Firefox has Object.prototype.watch
return options.watch && options.hasOwnProperty("watch");
}
export function getCheckFlags(symbol: Symbol): CheckFlags {
return symbol.flags & SymbolFlags.Transient ? (<TransientSymbol>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 = (<TransientSymbol>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;
}
}
namespace ts {

View file

@ -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);
}
@ -406,7 +409,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) {
@ -505,6 +508,7 @@ namespace ts.Completions {
let isGlobalCompletion = false;
let isMemberCompletion: boolean;
let isNewIdentifierLocation: boolean;
let hasFilteredClassMemberKeywords = false;
let symbols: Symbol[] = [];
if (isRightOfDot) {
@ -542,7 +546,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
@ -599,6 +603,7 @@ namespace ts.Completions {
function tryGetGlobalSymbols(): boolean {
let objectLikeContainer: ObjectLiteralExpression | BindingPattern;
let namedImportsOrExports: NamedImportsOrExports;
let classLikeContainer: ClassLikeDeclaration;
let jsxContainer: JsxOpeningLikeElement;
if (objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken)) {
@ -611,6 +616,11 @@ namespace ts.Completions {
return tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports);
}
if (classLikeContainer = tryGetClassLikeCompletionContainer(contextToken)) {
// cursor inside class declaration
return tryGetClassLikeCompletionSymbols(classLikeContainer);
}
if (jsxContainer = tryGetContainingJsxElement(contextToken)) {
let attrsType: Type;
if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) {
@ -913,6 +923,31 @@ namespace ts.Completions {
return true;
}
/**
* Aggregates relevant symbols for completion in class declaration
* Relevant symbols are stored in the captured 'symbols' variable.
*
* @returns true if 'symbols' was successfully populated; false otherwise.
*/
function tryGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration): boolean {
// 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);
if (baseTypeNode) {
const baseType = typeChecker.getTypeAtLocation(baseTypeNode);
// List of property symbols of base type that are not private
symbols = filter(typeChecker.getPropertiesOfType(baseType),
baseProperty => !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private));
}
return true;
}
/**
* 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 +988,38 @@ namespace ts.Completions {
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.
*/
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;
}
}
// 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;
@ -1306,6 +1373,21 @@ namespace ts.Completions {
});
}
const classMemberKeywordCompletions = filter(keywordCompletions, entry => {
switch (stringToToken(entry.name)) {
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:
return true;
}
});
function isEqualityExpression(node: Node): node is BinaryExpression {
return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind);
}

View file

@ -0,0 +1,142 @@
///<reference path="fourslash.ts" />
////abstract class B {
//// abstract getValue(): number;
//// /*abstractClass*/
////}
////class C extends B {
//// /*classThatIsEmptyAndExtendingAnotherClass*/
////}
////class D extends B {
//// /*classThatHasAlreadyImplementedAnotherClassMethod*/
//// getValue() {
//// return 10;
//// }
//// /*classThatHasAlreadyImplementedAnotherClassMethodAfterMethod*/
////}
////class E {
//// /*classThatDoesNotExtendAnotherClass*/
////}
////class F extends B {
//// public /*classThatHasWrittenPublicKeyword*/
////}
////class G extends B {
//// static /*classElementContainingStatic*/
////}
////class H extends B {
//// prop/*classThatStartedWritingIdentifier*/
////}
////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 L extends B {
//// static identi/*classThatStartedWritingIdentifierAfterStaticModifier*/
////}
const allowedKeywords = [
"public",
"private",
"protected",
"static",
"abstract",
"readonly",
"get",
"set",
"constructor"
];
const allowedKeywordCount = allowedKeywords.length;
function verifyAllowedKeyWords() {
for (const keyword of allowedKeywords) {
verify.completionListContains(keyword, keyword, /*documentation*/ undefined, "keyword");
}
}
const nonClassElementMarkers = [
"InsideMethod"
];
for (const marker of nonClassElementMarkers) {
goTo.marker(marker);
verify.not.completionListContains("getValue");
verify.not.completionListIsEmpty();
}
// Only keywords allowed at this position since they dont extend the class
const onlyClassElementKeywordLocations = [
"abstractClass",
"classThatDoesNotExtendAnotherClass"
];
for (const marker of onlyClassElementKeywordLocations) {
goTo.marker(marker);
verifyAllowedKeyWords();
verify.completionListCount(allowedKeywordCount);
}
// Base members and class member keywords allowed
const classElementCompletionLocations = [
"classThatIsEmptyAndExtendingAnotherClass",
"classThatHasAlreadyImplementedAnotherClassMethod",
"classThatHasAlreadyImplementedAnotherClassMethodAfterMethod",
// TODO should we give completion for these keywords
//"classThatHasWrittenPublicKeyword",
//"classElementContainingStatic",
"classThatStartedWritingIdentifier",
"propDeclarationWithoutSemicolon",
"propDeclarationWithSemicolon",
"propAssignmentWithSemicolon",
"propAssignmentWithoutSemicolon",
"methodSignatureWithoutSemicolon",
"methodSignatureWithSemicolon",
"methodImplementation",
"accessorSignatureWithoutSemicolon",
"accessorSignatureImplementation",
// TODO should we give completion for these keywords
//"classThatHasWrittenGetKeyword",
//"classThatHasWrittenSetKeyword",
//"classThatStartedWritingIdentifierOfGetAccessor",
//"classThatStartedWritingIdentifierOfSetAccessor",
//"classThatStartedWritingIdentifierAfterModifier",
//"classThatStartedWritingIdentifierAfterStaticModifier"
];
for (const marker of classElementCompletionLocations) {
goTo.marker(marker);
verify.completionListContains("getValue", "(method) B.getValue(): number", /*documentation*/ undefined, "method");
verifyAllowedKeyWords();
verify.completionListCount(allowedKeywordCount + 1);
}

View file

@ -1,4 +1,4 @@
///<reference path="fourslash.ts" />
///<reference path="fourslash.ts" />
//// 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");
verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class");

View file

@ -1,4 +1,4 @@
///<reference path="fourslash.ts" />
///<reference path="fourslash.ts" />
//// 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");

View file

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

View file

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

View file

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