diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 0dfcb4ac513..649dc3d6d1a 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -820,7 +820,14 @@ module FourSlash { this.taoInvalidReason = 'verifyCurrentSignatureHelpIs NYI'; var help = this.getActiveSignatureHelpItem(); - assert.equal(help.prefix + help.parameters.map(p => p.display).join(help.separator) + help.suffix, expected); + assert.equal( + this.partsToString(help.prefixDisplayParts) + + help.parameters.map(p => this.partsToString(p.displayParts)).join(this.partsToString(help.separatorDisplayParts)) + + this.partsToString(help.suffixDisplayParts), expected); + } + + private partsToString(parts: ts.SymbolDisplayPart[]): string { + return parts.map(p => p.text).join(""); } public verifyCurrentParameterIsVariable(isVariable: boolean) { @@ -844,7 +851,7 @@ module FourSlash { var activeSignature = this.getActiveSignatureHelpItem(); var activeParameter = this.getActiveParameter(); - assert.equal(activeParameter.display, parameter); + assert.equal(this.partsToString(activeParameter.displayParts), parameter); } public verifyCurrentParameterHelpDocComment(docComment: string) { diff --git a/src/services/services.ts b/src/services/services.ts index a2595473252..524fefc961e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -856,8 +856,8 @@ module ts { export class SignatureHelpParameter { constructor(public name: string, - public documentation: string, - public display: string, + public documentation: SymbolDisplayPart[], + public displayParts: SymbolDisplayPart[], public isOptional: boolean) { } } @@ -871,11 +871,11 @@ module ts { */ export class SignatureHelpItem { constructor(public isVariadic: boolean, - public prefix: string, - public suffix: string, - public separator: string, + public prefixDisplayParts: SymbolDisplayPart[], + public suffixDisplayParts: SymbolDisplayPart[], + public separatorDisplayParts: SymbolDisplayPart[], public parameters: SignatureHelpParameter[], - public documentation: string) { + public documentation: SymbolDisplayPart[]) { } } @@ -1630,6 +1630,11 @@ module ts { }); } + export function getSymbolDocumentationDisplayParts(symbol: Symbol): SymbolDisplayPart[] { + var documentation = symbol.getDocumentationComment(); + return documentation === "" ? [] : [new SymbolDisplayPart(documentation, SymbolDisplayPartKind.text, /*symbol:*/ null)]; + } + export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry): LanguageService { var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); var formattingRulesProvider: TypeScript.Services.Formatting.RulesProvider; @@ -2376,8 +2381,7 @@ module ts { return undefined; } - var documentation = symbol.getDocumentationComment(); - var documentationParts = documentation === "" ? [] : [new SymbolDisplayPart(documentation, SymbolDisplayPartKind.text, /*symbol:*/ null)]; + var documentationParts = getSymbolDocumentationDisplayParts(symbol); // Having all this logic here is pretty unclean. Consider moving to the roslyn model // where all symbol display logic is encapsulated into visitors and options. @@ -3731,9 +3735,9 @@ module ts { // Reset writer back to undefined to make sure that we produce an error message if CompilerHost.writeFile method is called when we are not in getEmitOutput writer = undefined; - return emitOutput; - } - + return emitOutput; + } + // Signature help /** * This is a semantic operation. diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index f129cabc00a..d8f8341a9f6 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -252,29 +252,66 @@ module ts.SignatureHelp { var items = map(candidates, candidateSignature => { var parameters = candidateSignature.parameters; var parameterHelpItems = parameters.length === 0 ? emptyArray : map(parameters, p => { - var display = p.name; + var displayParts: SymbolDisplayPart[] = []; + if (candidateSignature.hasRestParameter && parameters[parameters.length - 1] === p) { - display = "..." + display; + displayParts.push(new SymbolDisplayPart(tokenToString(SyntaxKind.DotDotDotToken), SymbolDisplayPartKind.punctuation, undefined)); } + + displayParts.push(new SymbolDisplayPart(p.name, SymbolDisplayPartKind.parameterName, p)); + var isOptional = !!(p.valueDeclaration.flags & NodeFlags.QuestionMark); if (isOptional) { - display += "?"; + displayParts.push(new SymbolDisplayPart(tokenToString(SyntaxKind.QuestionToken), SymbolDisplayPartKind.punctuation, undefined)); } - display += ": " + typeInfoResolver.typeToString(typeInfoResolver.getTypeOfSymbol(p), argumentListOrTypeArgumentList); - return new SignatureHelpParameter(p.name, "", display, isOptional); + + displayParts.push(new SymbolDisplayPart(tokenToString(SyntaxKind.ColonToken), SymbolDisplayPartKind.punctuation, undefined)); + displayParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + + var typeParts = typeInfoResolver.typeToDisplayParts(typeInfoResolver.getTypeOfSymbol(p), argumentListOrTypeArgumentList); + displayParts.push.apply(displayParts, typeParts); + + return new SignatureHelpParameter(p.name, getSymbolDocumentationDisplayParts(p), displayParts, isOptional); }); + var callTargetNode = (argumentListOrTypeArgumentList.parent).func; var callTargetSymbol = typeInfoResolver.getSymbolInfo(callTargetNode); - var signatureName = callTargetSymbol ? typeInfoResolver.symbolToString(callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined) : ""; - var prefix = signatureName; + + var prefixParts = callTargetSymbol ? typeInfoResolver.symbolToDisplayParts(callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined) : []; + + var separatorParts = [ + new SymbolDisplayPart(tokenToString(SyntaxKind.CommaToken), SymbolDisplayPartKind.punctuation, undefined), + new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined) + ]; + // TODO(jfreeman): Constraints? if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { - prefix += "<" + map(candidateSignature.typeParameters, tp => tp.symbol.name).join(", ") + ">"; + prefixParts.push(new SymbolDisplayPart(tokenToString(SyntaxKind.LessThanToken), SymbolDisplayPartKind.punctuation, undefined)); + + for (var i = 0, n = candidateSignature.typeParameters.length; i < n; i++) { + if (i) { + prefixParts.push.apply(prefixParts, separatorParts); + } + + var tp = candidateSignature.typeParameters[i].symbol; + prefixParts.push(new SymbolDisplayPart(tp.name, SymbolDisplayPartKind.typeParameterName, tp)); + } + + prefixParts.push(new SymbolDisplayPart(tokenToString(SyntaxKind.GreaterThanToken), SymbolDisplayPartKind.punctuation, undefined)); } - prefix += "("; - var suffix = "): " + typeInfoResolver.typeToString(candidateSignature.getReturnType(), argumentListOrTypeArgumentList); - return new SignatureHelpItem(candidateSignature.hasRestParameter, prefix, suffix, ", ", parameterHelpItems, ""); + + prefixParts.push(new SymbolDisplayPart(tokenToString(SyntaxKind.OpenParenToken), SymbolDisplayPartKind.punctuation, undefined)); + + var suffixParts = [new SymbolDisplayPart(tokenToString(SyntaxKind.CloseParenToken), SymbolDisplayPartKind.punctuation, undefined)]; + suffixParts.push(new SymbolDisplayPart(tokenToString(SyntaxKind.ColonToken), SymbolDisplayPartKind.punctuation, undefined)); + suffixParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + + var typeParts = typeInfoResolver.typeToDisplayParts(candidateSignature.getReturnType(), argumentListOrTypeArgumentList); + suffixParts.push.apply(suffixParts, typeParts); + + return new SignatureHelpItem(candidateSignature.hasRestParameter, prefixParts, suffixParts, separatorParts, parameterHelpItems, null); }); + var selectedItemIndex = candidates.indexOf(bestSignature); if (selectedItemIndex < 0) { selectedItemIndex = 0; @@ -339,11 +376,11 @@ module ts.SignatureHelp { // by 2 to exclude commas. Use bit shifting in order to take the floor of the division. var argumentIndex = indexOfNodeContainingPosition < 0 ? argumentCount - 1 : indexOfNodeContainingPosition >> 1; return new SignatureHelpState(argumentIndex, argumentCount); - } - - function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { + } + + function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { var children = parent.getChildren(sourceFile); var indexOfOpenerToken = children.indexOf(openerToken); - return children[indexOfOpenerToken + 1]; + return children[indexOfOpenerToken + 1]; } } \ No newline at end of file diff --git a/tests/cases/fourslash/signatureHelpSuperConstructorOverload.ts b/tests/cases/fourslash/signatureHelpSuperConstructorOverload.ts index 1be8c3202a1..28c47c051f9 100644 --- a/tests/cases/fourslash/signatureHelpSuperConstructorOverload.ts +++ b/tests/cases/fourslash/signatureHelpSuperConstructorOverload.ts @@ -17,6 +17,7 @@ //// } ////} +debugger; goTo.marker('superOverload1'); verify.signatureHelpCountIs(2); verify.currentSignatureHelpIs("SuperOverloadlBase(): SuperOverloadlBase");