diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1ec86615949..cf35c1c02f5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -925,38 +925,17 @@ module ts { var displayPartWriters: DisplayPartsSymbolWriter[] = []; var stringWriters: StringSymbolWriter[] = []; - function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { - var flags = symbol.flags; - - if (flags & SymbolFlags.Variable) { - return symbol.declarations && symbol.declarations.length > 0 && symbol.declarations[0].kind === SyntaxKind.Parameter - ? SymbolDisplayPartKind.parameterName - : SymbolDisplayPartKind.localName; - } - else if (flags & SymbolFlags.Property) { return SymbolDisplayPartKind.propertyName; } - else if (flags & SymbolFlags.EnumMember) { return SymbolDisplayPartKind.enumMemberName; } - else if (flags & SymbolFlags.Function) { return SymbolDisplayPartKind.functionName; } - else if (flags & SymbolFlags.Class) { return SymbolDisplayPartKind.className; } - else if (flags & SymbolFlags.Interface) { return SymbolDisplayPartKind.interfaceName; } - else if (flags & SymbolFlags.Enum) { return SymbolDisplayPartKind.enumName; } - else if (flags & SymbolFlags.Module) { return SymbolDisplayPartKind.moduleName; } - else if (flags & SymbolFlags.Method) { return SymbolDisplayPartKind.methodName; } - else if (flags & SymbolFlags.TypeParameter) { return SymbolDisplayPartKind.typeParameterName; } - - return SymbolDisplayPartKind.text; - } - function getDisplayPartWriter(): DisplayPartsSymbolWriter { if (displayPartWriters.length == 0) { var displayParts: SymbolDisplayPart[] = []; return { displayParts: () => displayParts, writeKind: (text, kind) => displayParts.push(new SymbolDisplayPart(text, kind, undefined)), - writeSymbol: (text, symbol) => displayParts.push(new SymbolDisplayPart(text, displayPartKind(symbol), symbol)), + writeSymbol: (text, symbol) => displayParts.push(symbolPart(text, symbol)), // Completely ignore indentation for display part writers. And map newlines to // a single space. - writeLine: () => displayParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)), + writeLine: () => displayParts.push(spacePart()), increaseIndent: () => { }, decreaseIndent: () => { }, clear: () => displayParts = [], @@ -7693,4 +7672,49 @@ module ts { return checker; } + + export function spacePart() { + return new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined); + } + + export function keywordPart(kind: SyntaxKind) { + return new SymbolDisplayPart(tokenToString(kind), SymbolDisplayPartKind.keyword, undefined); + } + + export function punctuationPart(kind: SyntaxKind) { + return new SymbolDisplayPart(tokenToString(kind), SymbolDisplayPartKind.punctuation, undefined); + } + + export function operatorPart(kind: SyntaxKind) { + return new SymbolDisplayPart(tokenToString(kind), SymbolDisplayPartKind.operator, undefined); + } + + export function textPart(text: string) { + return new SymbolDisplayPart(text, SymbolDisplayPartKind.text, undefined); + } + + export function symbolPart(text: string, symbol: Symbol) { + return new SymbolDisplayPart(text, displayPartKind(symbol), symbol) + } + + function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { + var flags = symbol.flags; + + if (flags & SymbolFlags.Variable) { + return symbol.declarations && symbol.declarations.length > 0 && symbol.declarations[0].kind === SyntaxKind.Parameter + ? SymbolDisplayPartKind.parameterName + : SymbolDisplayPartKind.localName; + } + else if (flags & SymbolFlags.Property) { return SymbolDisplayPartKind.propertyName; } + else if (flags & SymbolFlags.EnumMember) { return SymbolDisplayPartKind.enumMemberName; } + else if (flags & SymbolFlags.Function) { return SymbolDisplayPartKind.functionName; } + else if (flags & SymbolFlags.Class) { return SymbolDisplayPartKind.className; } + else if (flags & SymbolFlags.Interface) { return SymbolDisplayPartKind.interfaceName; } + else if (flags & SymbolFlags.Enum) { return SymbolDisplayPartKind.enumName; } + else if (flags & SymbolFlags.Module) { return SymbolDisplayPartKind.moduleName; } + else if (flags & SymbolFlags.Method) { return SymbolDisplayPartKind.methodName; } + else if (flags & SymbolFlags.TypeParameter) { return SymbolDisplayPartKind.typeParameterName; } + + return SymbolDisplayPartKind.text; + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 991e3096daa..dcc42bf3358 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1199,6 +1199,10 @@ module ts { kind: SymbolDisplayPartKind[this.kind] }; } + + public static toString(parts: SymbolDisplayPart[]) { + return parts.map(p => p.text).join(""); + } } export enum SymbolDisplayPartKind { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index cd553345d7c..1e499fd79be 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -820,7 +820,10 @@ 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( + ts.SymbolDisplayPart.toString(help.prefixDisplayParts) + + help.parameters.map(p => ts.SymbolDisplayPart.toString(p.displayParts)).join(ts.SymbolDisplayPart.toString(help.separatorDisplayParts)) + + ts.SymbolDisplayPart.toString(help.suffixDisplayParts), expected); } public verifyCurrentParameterIsVariable(isVariable: boolean) { @@ -844,7 +847,7 @@ module FourSlash { var activeSignature = this.getActiveSignatureHelpItem(); var activeParameter = this.getActiveParameter(); - assert.equal(activeParameter.display, parameter); + assert.equal(ts.SymbolDisplayPart.toString(activeParameter.displayParts), parameter); } public verifyCurrentParameterHelpDocComment(docComment: string) { diff --git a/src/services/services.ts b/src/services/services.ts index 2d70a1a1254..7d11d9ab32d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -722,7 +722,7 @@ module ts { currentParameterIsTypeParameter: boolean; // current parameter is a type argument or a normal argument currentParameter: number; // Index of active parameter in "parameters" or "typeParamters" array } - + export interface ClassifiedSpan { textSpan: TypeScript.TextSpan; classificationType: string; // ClassificationTypeNames @@ -833,8 +833,8 @@ module ts { export interface SignatureHelpParameter { name: string; - documentation: string; - display: string; + documentation: SymbolDisplayPart[]; + displayParts: SymbolDisplayPart[]; isOptional: boolean; } @@ -847,11 +847,11 @@ module ts { */ export interface SignatureHelpItem { isVariadic: boolean; - prefix: string; - suffix: string; - separator: string; + prefixDisplayParts: SymbolDisplayPart[]; + suffixDisplayParts: SymbolDisplayPart[]; + separatorDisplayParts: SymbolDisplayPart[]; parameters: SignatureHelpParameter[]; - documentation: string; + documentation: SymbolDisplayPart[]; } /** @@ -1622,6 +1622,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; @@ -2372,42 +2377,41 @@ 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. var totalParts: SymbolDisplayPart[] = []; if (symbol.flags & SymbolFlags.Class) { - totalParts.push(new SymbolDisplayPart("class", SymbolDisplayPartKind.keyword, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(keywordPart(SyntaxKind.ClassKeyword)); + totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } else if (symbol.flags & SymbolFlags.Interface) { - totalParts.push(new SymbolDisplayPart("interface", SymbolDisplayPartKind.keyword, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); + totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } else if (symbol.flags & SymbolFlags.Enum) { - totalParts.push(new SymbolDisplayPart("enum", SymbolDisplayPartKind.keyword, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(keywordPart(SyntaxKind.EnumKeyword)); + totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } else if (symbol.flags & SymbolFlags.Module) { - totalParts.push(new SymbolDisplayPart("module", SymbolDisplayPartKind.keyword, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(keywordPart(SyntaxKind.ModuleKeyword)); + totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } else if (symbol.flags & SymbolFlags.TypeParameter) { - totalParts.push(new SymbolDisplayPart("(", SymbolDisplayPartKind.punctuation, undefined)); + totalParts.push(punctuationPart(SyntaxKind.OpenParenToken)); totalParts.push(new SymbolDisplayPart("type parameter", SymbolDisplayPartKind.text, undefined)); - totalParts.push(new SymbolDisplayPart(")", SymbolDisplayPartKind.punctuation, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol)); } else { - totalParts.push(new SymbolDisplayPart("(", SymbolDisplayPartKind.punctuation, undefined)); + totalParts.push(punctuationPart(SyntaxKind.OpenParenToken)); var text: string; if (symbol.flags & SymbolFlags.Property) { text = "property" } @@ -2421,8 +2425,8 @@ module ts { } totalParts.push(new SymbolDisplayPart(text, SymbolDisplayPartKind.text, undefined)); - totalParts.push(new SymbolDisplayPart(")", SymbolDisplayPartKind.punctuation, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, getContainerNode(node))); @@ -2432,8 +2436,8 @@ module ts { symbol.flags & SymbolFlags.Variable) { if (type) { - totalParts.push(new SymbolDisplayPart(":", SymbolDisplayPartKind.punctuation, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(punctuationPart(SyntaxKind.ColonToken)); + totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.typeToDisplayParts(type, getContainerNode(node))); } } @@ -2448,9 +2452,9 @@ module ts { if (declaration.kind === SyntaxKind.EnumMember) { var constantValue = typeInfoResolver.getEnumMemberValue(declaration); if (constantValue !== undefined) { - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); - totalParts.push(new SymbolDisplayPart("=", SymbolDisplayPartKind.operator, undefined)); - totalParts.push(new SymbolDisplayPart(" ", SymbolDisplayPartKind.space, undefined)); + totalParts.push(spacePart()); + totalParts.push(operatorPart(SyntaxKind.EqualsToken)); + totalParts.push(spacePart()); totalParts.push(new SymbolDisplayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral, undefined)); } } @@ -3782,7 +3786,7 @@ module ts { var formalSignatures: FormalSignatureItemInfo[] = []; forEach(signatureHelpItems.items, signature => { - var signatureInfoString = signature.prefix; + var signatureInfoString = ts.SymbolDisplayPart.toString(signature.prefixDisplayParts); var parameters: FormalParameterInfo[] = []; if (signature.parameters) { @@ -3791,32 +3795,31 @@ module ts { // add the parameter to the string if (i) { - signatureInfoString += signature.separator; + signatureInfoString += ts.SymbolDisplayPart.toString(signature.separatorDisplayParts); } var start = signatureInfoString.length; - signatureInfoString += parameter.display; + signatureInfoString += ts.SymbolDisplayPart.toString(parameter.displayParts); var end = signatureInfoString.length - 1; // add the parameter to the list parameters.push({ name: parameter.name, isVariable: i == n - 1 && signature.isVariadic, - docComment: parameter.documentation, + docComment: ts.SymbolDisplayPart.toString(parameter.documentation), minChar: start, limChar: end }); } } - signatureInfoString += signature.suffix; + signatureInfoString += ts.SymbolDisplayPart.toString(signature.suffixDisplayParts); formalSignatures.push({ signatureInfo: signatureInfoString, - docComment: signature.documentation, + docComment: ts.SymbolDisplayPart.toString(signature.documentation), parameters: parameters, typeParameters: [], - docComments: signature.documentation }); }); diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index b4ed515cf97..4a02c99204d 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -249,39 +249,78 @@ module ts.SignatureHelp { } function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListOrTypeArgumentList: Node): SignatureHelpItems { - var items = map(candidates, candidateSignature => { + var items: SignatureHelpItem[] = map(candidates, candidateSignature => { var parameters = candidateSignature.parameters; - var parameterHelpItems = parameters.length === 0 ? emptyArray : map(parameters, p => { - var display = p.name; + var parameterHelpItems: SignatureHelpParameter[] = parameters.length === 0 ? emptyArray : map(parameters, p => { + var displayParts: SymbolDisplayPart[] = []; + if (candidateSignature.hasRestParameter && parameters[parameters.length - 1] === p) { - display = "..." + display; + displayParts.push(punctuationPart(SyntaxKind.DotDotDotToken)); } + + displayParts.push(symbolPart(p.name, p)); + var isOptional = !!(p.valueDeclaration.flags & NodeFlags.QuestionMark); if (isOptional) { - display += "?"; + displayParts.push(punctuationPart(SyntaxKind.QuestionToken)); } - display += ": " + typeInfoResolver.typeToString(typeInfoResolver.getTypeOfSymbol(p), argumentListOrTypeArgumentList); - return { name: p.name, documentation: "", display: display, isOptiona: isOptional }; + + displayParts.push(punctuationPart(SyntaxKind.ColonToken)); + displayParts.push(spacePart()); + + var typeParts = typeInfoResolver.typeToDisplayParts(typeInfoResolver.getTypeOfSymbol(p), argumentListOrTypeArgumentList); + displayParts.push.apply(displayParts, typeParts); + + return { + name: p.name, + documentation: getSymbolDocumentationDisplayParts(p), + displayParts: displayParts, + isOptional: 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 = [punctuationPart(SyntaxKind.CommaToken), spacePart()]; + // TODO(jfreeman): Constraints? if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { - prefix += "<" + map(candidateSignature.typeParameters, tp => tp.symbol.name).join(", ") + ">"; + prefixParts.push(punctuationPart(SyntaxKind.LessThanToken)); + + 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(symbolPart(tp.name, tp)); + } + + prefixParts.push(punctuationPart(SyntaxKind.GreaterThanToken)); } - prefix += "("; - var suffix = "): " + typeInfoResolver.typeToString(candidateSignature.getReturnType(), argumentListOrTypeArgumentList); + + prefixParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + + var suffixParts = [punctuationPart(SyntaxKind.CloseParenToken)]; + suffixParts.push(punctuationPart(SyntaxKind.ColonToken)); + suffixParts.push(spacePart()); + + var typeParts = typeInfoResolver.typeToDisplayParts(candidateSignature.getReturnType(), argumentListOrTypeArgumentList); + suffixParts.push.apply(suffixParts, typeParts); + return { isVariadic: candidateSignature.hasRestParameter, - prefix: prefix, - suffix: suffix, - separator: ", ", + prefixDisplayParts: prefixParts, + suffixDisplayParts: suffixParts, + separatorDisplayParts: separatorParts, parameters: parameterHelpItems, - documentation: "" + documentation: null }; }); + var selectedItemIndex = candidates.indexOf(bestSignature); if (selectedItemIndex < 0) { selectedItemIndex = 0;