Give the class element completion on typing keywords like public, private, readonly

Also when name of the function is location, make sure we are actually looking at the same symbol before using the declaration to get signature to display
This commit is contained in:
Sheetal Nandi 2017-05-01 11:45:18 -07:00
parent b3d793608d
commit 37a2cddabc
7 changed files with 111 additions and 60 deletions

View file

@ -3417,6 +3417,17 @@ namespace FourSlashInterface {
export class VerifyNegatable {
public not: VerifyNegatable;
public allowedClassElementKeywords = [
"public",
"private",
"protected",
"static",
"abstract",
"readonly",
"get",
"set",
"constructor"
];
constructor(protected state: FourSlash.TestState, private negative = false) {
if (!negative) {
@ -3453,6 +3464,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);
}

View file

@ -988,6 +988,10 @@ 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.
@ -1010,6 +1014,13 @@ namespace ts.Completions {
return location;
}
break;
default:
if (isFromClassElementDeclaration(contextToken) &&
(isClassMemberCompletionKeyword(contextToken.kind) ||
isClassMemberCompletionKeywordText(contextToken.getText()))) {
return contextToken.parent.parent as ClassLikeDeclaration;
}
}
}
@ -1148,7 +1159,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 ||
@ -1165,13 +1176,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:
@ -1180,6 +1195,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":
@ -1373,8 +1395,8 @@ namespace ts.Completions {
});
}
const classMemberKeywordCompletions = filter(keywordCompletions, entry => {
switch (stringToToken(entry.name)) {
function isClassMemberCompletionKeyword(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.PublicKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PrivateKeyword:
@ -1386,7 +1408,14 @@ namespace ts.Completions {
case SyntaxKind.SetKeyword:
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);

View file

@ -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 = <FunctionLikeDeclaration>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;
}
}
}
}

View file

@ -1,6 +1,9 @@
///<reference path="fourslash.ts" />
////abstract class B {
//// private privateMethod() { }
//// protected protectedMethod() { };
//// static staticMethod() { }
//// abstract getValue(): number;
//// /*abstractClass*/
////}
@ -69,25 +72,7 @@
//// 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 allowedKeywordCount = verify.allowedClassElementKeywords.length;
const nonClassElementMarkers = [
"InsideMethod"
];
@ -104,7 +89,7 @@ const onlyClassElementKeywordLocations = [
];
for (const marker of onlyClassElementKeywordLocations) {
goTo.marker(marker);
verifyAllowedKeyWords();
verify.completionListContainsClassElementKeywords();
verify.completionListCount(allowedKeywordCount);
}
@ -113,9 +98,8 @@ const classElementCompletionLocations = [
"classThatIsEmptyAndExtendingAnotherClass",
"classThatHasAlreadyImplementedAnotherClassMethod",
"classThatHasAlreadyImplementedAnotherClassMethodAfterMethod",
// TODO should we give completion for these keywords
//"classThatHasWrittenPublicKeyword",
//"classElementContainingStatic",
"classThatHasWrittenPublicKeyword",
"classElementContainingStatic",
"classThatStartedWritingIdentifier",
"propDeclarationWithoutSemicolon",
"propDeclarationWithSemicolon",
@ -126,17 +110,30 @@ const classElementCompletionLocations = [
"methodImplementation",
"accessorSignatureWithoutSemicolon",
"accessorSignatureImplementation",
// TODO should we give completion for these keywords
//"classThatHasWrittenGetKeyword",
//"classThatHasWrittenSetKeyword",
//"classThatStartedWritingIdentifierOfGetAccessor",
//"classThatStartedWritingIdentifierOfSetAccessor",
//"classThatStartedWritingIdentifierAfterModifier",
//"classThatStartedWritingIdentifierAfterStaticModifier"
"classThatHasWrittenGetKeyword",
"classThatHasWrittenSetKeyword",
"classThatStartedWritingIdentifierOfGetAccessor",
"classThatStartedWritingIdentifierOfSetAccessor",
"classThatStartedWritingIdentifierAfterModifier",
"classThatStartedWritingIdentifierAfterStaticModifier"
];
const validMembersOfBase = [
["getValue", "(method) B.getValue(): number"],
["protectedMethod", "(method) B.protectedMethod(): void"]
];
const invalidMembersOfBase = [
["privateMethod", "(method) B.privateMethod(): void"],
["staticMethod", "(method) B.staticMethod(): void"]
];
for (const marker of classElementCompletionLocations) {
goTo.marker(marker);
verify.completionListContains("getValue", "(method) B.getValue(): number", /*documentation*/ undefined, "method");
verifyAllowedKeyWords();
verify.completionListCount(allowedKeywordCount + 1);
for (const [validMemberOfBaseSymbol, validMemberOfBaseText] of validMembersOfBase) {
verify.completionListContains(validMemberOfBaseSymbol, validMemberOfBaseText, /*documentation*/ undefined, "method");
}
for (const [invalidMemberOfBaseSymbol, invalidMemberOfBaseText] of invalidMembersOfBase) {
verify.not.completionListContains(invalidMemberOfBaseSymbol, invalidMemberOfBaseText, /*documentation*/ undefined, "method");
}
verify.completionListContainsClassElementKeywords();
verify.completionListCount(allowedKeywordCount + validMembersOfBase.length);
}

View file

@ -10,4 +10,4 @@
//// public static a/*property2*/
////}
goTo.eachMarker(() => verify.completionListIsEmpty());
goTo.eachMarker(() => verify.completionListContainsClassElementKeywords());

View file

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

View file

@ -26,7 +26,7 @@ const methods = ranges.get("method");
const [m0, m1, m2] = methods;
verify.referenceGroups(m0, [{ definition: "(method) Base<T>.method<U>(a?: T, b?: U): this", ranges: methods }]);
verify.referenceGroups(m1, [
{ definition: "(method) Base<T>.method(): void", ranges: [m0] },
{ definition: "(method) Base<T>.method<U>(a?: T, b?: U): this", ranges: [m0] },
{ definition: "(method) MyClass.method(): void", ranges: [m1, m2] }
]);
verify.referenceGroups(m2, [