/
githubmirror
/
TypeScript
Обзор
Документация
Войти
/
githubmirror
/
TypeScript
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/services/symbolDisplay.ts
1 064 строки
50 KB
Mateusz Burzyński
Don't return `tags` when returning quick info for JSDoc nodes (#59816)
30 июн 2025, 23:20
Не верифицирован
30 июн 2025, 23:20
58665cf
Код
Авторство
О чём код?
import { AccessorDeclaration, addRange, arrayFrom, BinaryExpression, CallExpression, CheckFlags, contains, createPrinterWithRemoveComments, Debug, displayPart, EmitHint, emptyArray, EnumMember, ExportAssignment, find, first, firstDefined, forEach, getCombinedLocalAndExportSymbolFlags, getDeclarationOfKind, getExternalModuleImportEqualsDeclarationExpression, getMeaningFromLocation, getNameOfDeclaration, getNodeModifiers, getObjectFlags, getParseTreeNode, getSourceFileOfNode, getTextOfConstantValue, getTextOfIdentifierOrLiteral, getTextOfNode, hasSyntacticModifier, idText, ImportEqualsDeclaration, isArrowFunction, isBindingElement, isCallExpression, isCallExpressionTarget, isCallOrNewExpression, isClassExpression, isConstTypeReference, isDeprecatedDeclaration, isEnumConst, isEnumDeclaration, isExpression, isExternalModuleImportEqualsDeclaration, isFirstDeclarationOfSymbolParameter, isFunctionBlock, isFunctionExpression, isFunctionLike, isIdentifier, isInExpressionContext, isInJSDoc, isJsxOpeningLikeElement, isLet, isModuleWithStringLiteralName, isNameOfFunctionDeclaration, isNewExpressionTarget, isObjectBindingPattern, isTaggedTemplateExpression, isThisInTypeQuery, isTransientSymbol, isTypeAliasDeclaration, isVarAwaitUsing, isVarConst, isVarUsing, JSDocTagInfo, JsxOpeningLikeElement, keywordPart, length, lineBreakPart, ListFormat, mapToDisplayParts, ModifierFlags, ModuleDeclaration, NewExpression, Node, NodeBuilderFlags, ObjectFlags, operatorPart, PropertyAccessExpression, PropertyDeclaration, punctuationPart, ScriptElementKind, ScriptElementKindModifier, SemanticMeaning, Signature, SignatureDeclaration, SignatureFlags, signatureToDisplayParts, some, SourceFile, spacePart, Symbol, SymbolDisplayPart, SymbolDisplayPartKind, SymbolFlags, SymbolFormatFlags, symbolToDisplayParts, SyntaxKind, TaggedTemplateExpression, textOrKeywordPart, textPart, TransientSymbol, Type, TypeChecker, TypeFormatFlags, TypeParameter, typeToDisplayParts, VariableDeclaration, WriterContextOut, } from "./_namespaces/ts.js"; const symbolDisplayNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; // TODO(drosen): use contextual SemanticMeaning. /** @internal */ export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); if (result !== ScriptElementKind.unknown) { return result; } const flags = getCombinedLocalAndExportSymbolFlags(symbol); if (flags & SymbolFlags.Class) { return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? ScriptElementKind.localClassElement : ScriptElementKind.classElement; } if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement; if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement; if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement; if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; if (flags & SymbolFlags.EnumMember) return ScriptElementKind.enumMemberElement; if (flags & SymbolFlags.Alias) return ScriptElementKind.alias; if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; return result; } function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { const roots = typeChecker.getRootSymbols(symbol); // If this is a method from a mapped type, leave as a method so long as it still has a call signature. if ( roots.length === 1 && first(roots).flags & SymbolFlags.Method // Ensure the mapped version is still a method, as opposed to `{ [K in keyof I]: number }`. && typeChecker.getTypeOfSymbolAtLocation(symbol, location).getNonNullableType().getCallSignatures().length !== 0 ) { return ScriptElementKind.memberFunctionElement; } if (typeChecker.isUndefinedSymbol(symbol)) { return ScriptElementKind.variableElement; } if (typeChecker.isArgumentsSymbol(symbol)) { return ScriptElementKind.localVariableElement; } if (location.kind === SyntaxKind.ThisKeyword && isExpression(location) || isThisInTypeQuery(location)) { return ScriptElementKind.parameterElement; } const flags = getCombinedLocalAndExportSymbolFlags(symbol); if (flags & SymbolFlags.Variable) { if (isFirstDeclarationOfSymbolParameter(symbol)) { return ScriptElementKind.parameterElement; } else if (symbol.valueDeclaration && isVarConst(symbol.valueDeclaration as VariableDeclaration)) { return ScriptElementKind.constElement; } else if (symbol.valueDeclaration && isVarUsing(symbol.valueDeclaration as VariableDeclaration)) { return ScriptElementKind.variableUsingElement; } else if (symbol.valueDeclaration && isVarAwaitUsing(symbol.valueDeclaration as VariableDeclaration)) { return ScriptElementKind.variableAwaitUsingElement; } else if (forEach(symbol.declarations, isLet)) { return ScriptElementKind.letElement; } return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement; } if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement; // FIXME: getter and setter use the same symbol. And it is rare to use only setter without getter, so in most cases the symbol always has getter flag. // So, even when the location is just on the declaration of setter, this function returns getter. if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement; if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement; if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement; if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; if (flags & SymbolFlags.Signature) return ScriptElementKind.indexSignatureElement; if (flags & SymbolFlags.Property) { if (flags & SymbolFlags.Transient && (symbol as TransientSymbol).links.checkFlags & CheckFlags.Synthetic) { // If union property is result of union of non method (property/accessors/variables), it is labeled as property const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { const rootSymbolFlags = rootSymbol.getFlags(); if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) { return ScriptElementKind.memberVariableElement; } }); if (!unionPropertyKind) { // If this was union of all methods, // make sure it has call signatures before we can label it as method const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location); if (typeOfUnionProperty.getCallSignatures().length) { return ScriptElementKind.memberFunctionElement; } return ScriptElementKind.memberVariableElement; } return unionPropertyKind; } return ScriptElementKind.memberVariableElement; } return ScriptElementKind.unknown; } function getNormalizedSymbolModifiers(symbol: Symbol) { if (symbol.declarations && symbol.declarations.length) { const [declaration, ...declarations] = symbol.declarations; // omit deprecated flag if some declarations are not deprecated const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) ? ModifierFlags.Deprecated : ModifierFlags.None; const modifiers = getNodeModifiers(declaration, excludeFlags); if (modifiers) { return modifiers.split(","); } } return []; } /** @internal */ export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string { if (!symbol) { return ScriptElementKindModifier.none; } const modifiers = new Set(getNormalizedSymbolModifiers(symbol)); if (symbol.flags & SymbolFlags.Alias) { const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); if (resolvedSymbol !== symbol) { forEach(getNormalizedSymbolModifiers(resolvedSymbol), modifier => { modifiers.add(modifier); }); } } if (symbol.flags & SymbolFlags.Optional) { modifiers.add(ScriptElementKindModifier.optionalModifier); } return modifiers.size > 0 ? arrayFrom(modifiers.values()).join(",") : ScriptElementKindModifier.none; } /** @internal */ export interface SymbolDisplayPartsDocumentationAndSymbolKind { displayParts: SymbolDisplayPart[]; documentation: SymbolDisplayPart[]; symbolKind: ScriptElementKind; tags: JSDocTagInfo[] | undefined; canIncreaseVerbosityLevel?: boolean; } function getSymbolDisplayPartsDocumentationAndSymbolKindWorker( typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, location: Node, type: Type | undefined, semanticMeaning: SemanticMeaning, alias?: Symbol, maximumLength?: number, verbosityLevel?: number, ): SymbolDisplayPartsDocumentationAndSymbolKind { const displayParts: SymbolDisplayPart[] = []; let documentation: SymbolDisplayPart[] = []; let tags: JSDocTagInfo[] = []; const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol); let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown; let hasAddedSymbolInfo = false; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location) || isThisInTypeQuery(location); let documentationFromAlias: SymbolDisplayPart[] | undefined; let tagsFromAlias: JSDocTagInfo[] | undefined; let hasMultipleSignatures = false; const typeWriterOut: WriterContextOut = { canIncreaseExpansionDepth: false, truncated: false }; let symbolWasExpanded = false; if (location.kind === SyntaxKind.ThisKeyword && !isThisExpression) { return { displayParts: [keywordPart(SyntaxKind.ThisKeyword)], documentation: [], symbolKind: ScriptElementKind.primitiveType, tags: undefined }; } // Class at constructor site need to be shown as constructor apart from property,method, vars if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) { // If symbol is accessor, they are allowed only if location is at declaration identifier of the accessor if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) { const declaration = find( symbol.declarations as (AccessorDeclaration | PropertyDeclaration | PropertyAccessExpression)[], (declaration): declaration is AccessorDeclaration | PropertyDeclaration => declaration.name === location // an expando member could have been added to an object with a set accessor // we need to ignore such write location as it shouldn't be displayed as `(setter)` anyway && declaration.kind !== SyntaxKind.PropertyAccessExpression, ); if (declaration) { switch (declaration.kind) { case SyntaxKind.GetAccessor: symbolKind = ScriptElementKind.memberGetAccessorElement; break; case SyntaxKind.SetAccessor: symbolKind = ScriptElementKind.memberSetAccessorElement; break; case SyntaxKind.PropertyDeclaration: symbolKind = ScriptElementKind.memberAccessorVariableElement; break; default: Debug.assertNever(declaration); } } else { symbolKind = ScriptElementKind.memberVariableElement; } } let signature: Signature | undefined; type ??= isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { const right = (location.parent as PropertyAccessExpression).name; // Either the location is on the right of a property access, or on the left and the right is missing if (right === location || (right && right.getFullWidth() === 0)) { location = location.parent; } } // try get the call/construct signature from the type if it matches let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement | TaggedTemplateExpression | undefined; if (isCallOrNewExpression(location)) { callExpressionLike = location; } else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { callExpressionLike = location.parent as CallExpression | NewExpression; } else if (location.parent && (isJsxOpeningLikeElement(location.parent) || isTaggedTemplateExpression(location.parent)) && isFunctionLike(symbol.valueDeclaration)) { callExpressionLike = location.parent; } if (callExpressionLike) { signature = typeChecker.getResolvedSignature(callExpressionLike); // TODO: GH#18217 const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); if (signature && !contains(allSignatures, signature.target) && !contains(allSignatures, signature)) { // Get the first signature if there is one -- allSignatures may contain // either the original signature or its target, so check for either signature = allSignatures.length ? allSignatures[0] : undefined; } if (signature) { if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) { // Constructor symbolKind = ScriptElementKind.constructorImplementationElement; addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); } else if (symbolFlags & SymbolFlags.Alias) { symbolKind = ScriptElementKind.alias; pushSymbolKind(symbolKind); displayParts.push(spacePart()); if (useConstructSignatures) { if (signature.flags & SignatureFlags.Abstract) { displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); displayParts.push(spacePart()); } displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); } addFullSymbolName(symbol); } else { addPrefixForAnyFunctionOrVar(symbol, symbolKind); } switch (symbolKind) { case ScriptElementKind.jsxAttribute: case ScriptElementKind.memberVariableElement: case ScriptElementKind.variableElement: case ScriptElementKind.constElement: case ScriptElementKind.letElement: case ScriptElementKind.parameterElement: case ScriptElementKind.localVariableElement: // If it is call or construct signature of lambda's write type name displayParts.push(punctuationPart(SyntaxKind.ColonToken)); displayParts.push(spacePart()); if (!(getObjectFlags(type) & ObjectFlags.Anonymous) && type.symbol) { addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteTypeParametersOrArguments)); displayParts.push(lineBreakPart()); } if (useConstructSignatures) { if (signature.flags & SignatureFlags.Abstract) { displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); displayParts.push(spacePart()); } displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); } addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature); break; default: // Just signature addSignatureDisplayParts(signature, allSignatures); } hasAddedSymbolInfo = true; hasMultipleSignatures = allSignatures.length > 1; } } else if ( (isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration (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 as SignatureDeclaration; // Use function declaration to write the signatures only if the symbol corresponding to this declaration const locationIsSymbolDeclaration = symbol.declarations && find(symbol.declarations, declaration => declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); if (locationIsSymbolDeclaration) { const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); // TODO: GH#18217 } else { signature = allSignatures[0]; } 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 (signature) { addSignatureDisplayParts(signature, allSignatures); } hasAddedSymbolInfo = true; hasMultipleSignatures = allSignatures.length > 1; } } } if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) { addAliasPrefixIfNecessary(); const classExpression = getDeclarationOfKind(symbol, SyntaxKind.ClassExpression); if (classExpression) { // Special case for class expressions because we would like to indicate that // the class name is local to the class body (similar to function expression) // (local class) class <className> pushSymbolKind(ScriptElementKind.localClassElement); displayParts.push(spacePart()); } if (!tryExpandSymbol(symbol, semanticMeaning)) { if (!classExpression) { // Class declaration has name which is not local. displayParts.push(keywordPart(SyntaxKind.ClassKeyword)); displayParts.push(spacePart()); } addFullSymbolName(symbol); writeTypeParametersOfSymbol(symbol, sourceFile); } } if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) { prefixNextMeaning(); if (!tryExpandSymbol(symbol, semanticMeaning)) { displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); displayParts.push(spacePart()); addFullSymbolName(symbol); writeTypeParametersOfSymbol(symbol, sourceFile); } } if ((symbolFlags & SymbolFlags.TypeAlias) && (semanticMeaning & SemanticMeaning.Type)) { prefixNextMeaning(); displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); displayParts.push(spacePart()); addFullSymbolName(symbol); writeTypeParametersOfSymbol(symbol, sourceFile); displayParts.push(spacePart()); displayParts.push(operatorPart(SyntaxKind.EqualsToken)); displayParts.push(spacePart()); addRange( displayParts, typeToDisplayParts( typeChecker, location.parent && isConstTypeReference(location.parent) ? typeChecker.getTypeAtLocation(location.parent) : typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias, maximumLength, verbosityLevel, typeWriterOut, ), ); } if (symbolFlags & SymbolFlags.Enum) { prefixNextMeaning(); if (!tryExpandSymbol(symbol, semanticMeaning)) { if (some(symbol.declarations, d => isEnumDeclaration(d) && isEnumConst(d))) { displayParts.push(keywordPart(SyntaxKind.ConstKeyword)); displayParts.push(spacePart()); } displayParts.push(keywordPart(SyntaxKind.EnumKeyword)); displayParts.push(spacePart()); addFullSymbolName(symbol, /*enclosingDeclaration*/ undefined); } } if (symbolFlags & SymbolFlags.Module && !isThisExpression) { prefixNextMeaning(); if (!tryExpandSymbol(symbol, semanticMeaning)) { const declaration = getDeclarationOfKind<ModuleDeclaration>(symbol, SyntaxKind.ModuleDeclaration); const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier; displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword)); displayParts.push(spacePart()); addFullSymbolName(symbol); } } if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) { prefixNextMeaning(); displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); displayParts.push(textPart("type parameter")); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); displayParts.push(spacePart()); addFullSymbolName(symbol); if (symbol.parent) { // Class/Interface type parameter addInPrefix(); addFullSymbolName(symbol.parent, enclosingDeclaration); writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration); } else { // Method/function type parameter const decl = getDeclarationOfKind(symbol, SyntaxKind.TypeParameter); if (decl === undefined) return Debug.fail(); const declaration = decl.parent; if (declaration) { if (isFunctionLike(declaration)) { addInPrefix(); const signature = typeChecker.getSignatureFromDeclaration(declaration)!; // TODO: GH#18217 if (declaration.kind === SyntaxKind.ConstructSignature) { displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); } else if (declaration.kind !== SyntaxKind.CallSignature && declaration.name) { addFullSymbolName(declaration.symbol); } addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature)); } else if (isTypeAliasDeclaration(declaration)) { // Type alias type parameter // For example // type list<T> = T[]; // Both T will go through same code path addInPrefix(); displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); displayParts.push(spacePart()); addFullSymbolName(declaration.symbol); writeTypeParametersOfSymbol(declaration.symbol, sourceFile); } } } } if (symbolFlags & SymbolFlags.EnumMember) { symbolKind = ScriptElementKind.enumMemberElement; addPrefixForAnyFunctionOrVar(symbol, "enum member"); const declaration = symbol.declarations?.[0]; if (declaration?.kind === SyntaxKind.EnumMember) { const constantValue = typeChecker.getConstantValue(declaration as EnumMember); if (constantValue !== undefined) { displayParts.push(spacePart()); displayParts.push(operatorPart(SyntaxKind.EqualsToken)); displayParts.push(spacePart()); displayParts.push(displayPart(getTextOfConstantValue(constantValue), typeof constantValue === "number" ? SymbolDisplayPartKind.numericLiteral : SymbolDisplayPartKind.stringLiteral)); } } } // don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself if (symbol.flags & SymbolFlags.Alias) { prefixNextMeaning(); if (!hasAddedSymbolInfo || documentation.length === 0 && tags.length === 0) { const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { const resolvedNode = resolvedSymbol.declarations[0]; const declarationName = getNameOfDeclaration(resolvedNode); if (declarationName && !hasAddedSymbolInfo) { const isExternalModuleDeclaration = isModuleWithStringLiteralName(resolvedNode) && hasSyntacticModifier(resolvedNode, ModifierFlags.Ambient); const shouldUseAliasName = symbol.name !== "default" && !isExternalModuleDeclaration; const resolvedInfo = getSymbolDisplayPartsDocumentationAndSymbolKindWorker( typeChecker, resolvedSymbol, getSourceFileOfNode(resolvedNode), enclosingDeclaration, declarationName, type, semanticMeaning, shouldUseAliasName ? symbol : resolvedSymbol, maximumLength, verbosityLevel, ); displayParts.push(...resolvedInfo.displayParts); displayParts.push(lineBreakPart()); documentationFromAlias = resolvedInfo.documentation; tagsFromAlias = resolvedInfo.tags; if (typeWriterOut && resolvedInfo.canIncreaseVerbosityLevel) { typeWriterOut.canIncreaseExpansionDepth = true; } } else { documentationFromAlias = resolvedSymbol.getContextualDocumentationComment(resolvedNode, typeChecker); tagsFromAlias = resolvedSymbol.getJsDocTags(typeChecker); } } } if (symbol.declarations) { switch (symbol.declarations[0].kind) { case SyntaxKind.NamespaceExportDeclaration: displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); displayParts.push(spacePart()); displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword)); break; case SyntaxKind.ExportAssignment: displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); displayParts.push(spacePart()); displayParts.push(keywordPart((symbol.declarations[0] as ExportAssignment).isExportEquals ? SyntaxKind.EqualsToken : SyntaxKind.DefaultKeyword)); break; case SyntaxKind.ExportSpecifier: displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); break; default: displayParts.push(keywordPart(SyntaxKind.ImportKeyword)); } } displayParts.push(spacePart()); addFullSymbolName(symbol); forEach(symbol.declarations, declaration => { if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) { const importEqualsDeclaration = declaration as ImportEqualsDeclaration; if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) { displayParts.push(spacePart()); displayParts.push(operatorPart(SyntaxKind.EqualsToken)); displayParts.push(spacePart()); displayParts.push(keywordPart(SyntaxKind.RequireKeyword)); displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral)); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } else { const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference); if (internalAliasSymbol) { displayParts.push(spacePart()); displayParts.push(operatorPart(SyntaxKind.EqualsToken)); displayParts.push(spacePart()); addFullSymbolName(internalAliasSymbol, enclosingDeclaration); } } return true; } }); } if (!hasAddedSymbolInfo) { if (symbolKind !== ScriptElementKind.unknown) { if (type) { if (isThisExpression) { prefixNextMeaning(); displayParts.push(keywordPart(SyntaxKind.ThisKeyword)); } else { addPrefixForAnyFunctionOrVar(symbol, symbolKind); } // For properties, variables and local vars: show the type if ( symbolKind === ScriptElementKind.memberVariableElement || symbolKind === ScriptElementKind.memberAccessorVariableElement || symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement || symbolKind === ScriptElementKind.jsxAttribute || symbolFlags & SymbolFlags.Variable || symbolKind === ScriptElementKind.localVariableElement || symbolKind === ScriptElementKind.indexSignatureElement || symbolKind === ScriptElementKind.variableUsingElement || symbolKind === ScriptElementKind.variableAwaitUsingElement || isThisExpression ) { displayParts.push(punctuationPart(SyntaxKind.ColonToken)); displayParts.push(spacePart()); // If the type is type parameter, format it specially if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter && symbolKind !== ScriptElementKind.indexSignatureElement) { const typeParameterParts = mapToDisplayParts(writer => { const param = typeChecker.typeParameterToDeclaration( type as TypeParameter, enclosingDeclaration, symbolDisplayNodeBuilderFlags, /*internalFlags*/ undefined, /*tracker*/ undefined, maximumLength, verbosityLevel, typeWriterOut, )!; getPrinter().writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); }, maximumLength); addRange(displayParts, typeParameterParts); } else { addRange( displayParts, typeToDisplayParts( typeChecker, type, enclosingDeclaration, /*flags*/ undefined, maximumLength, verbosityLevel, typeWriterOut, ), ); } if (isTransientSymbol(symbol) && symbol.links.target && isTransientSymbol(symbol.links.target) && symbol.links.target.links.tupleLabelDeclaration) { const labelDecl = symbol.links.target.links.tupleLabelDeclaration; Debug.assertNode(labelDecl.name, isIdentifier); displayParts.push(spacePart()); displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); displayParts.push(textPart(idText(labelDecl.name))); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } } else if ( symbolFlags & SymbolFlags.Function || symbolFlags & SymbolFlags.Method || symbolFlags & SymbolFlags.Constructor || symbolFlags & SymbolFlags.Signature || symbolFlags & SymbolFlags.Accessor || symbolKind === ScriptElementKind.memberFunctionElement ) { const allSignatures = type.getNonNullableType().getCallSignatures(); if (allSignatures.length) { addSignatureDisplayParts(allSignatures[0], allSignatures); hasMultipleSignatures = allSignatures.length > 1; } } } } else { symbolKind = getSymbolKind(typeChecker, symbol, location); } } if (documentation.length === 0 && !hasMultipleSignatures) { documentation = symbol.getContextualDocumentationComment(enclosingDeclaration, typeChecker); } if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) { // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` // there documentation comments might be attached to the right hand side symbol of their declarations. // The pattern of such special property access is that the parent symbol is the symbol of the file. if (symbol.parent && symbol.declarations && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) { for (const declaration of symbol.declarations) { if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) { continue; } const rhsSymbol = typeChecker.getSymbolAtLocation((declaration.parent as BinaryExpression).right); if (!rhsSymbol) { continue; } documentation = rhsSymbol.getDocumentationComment(typeChecker); tags = rhsSymbol.getJsDocTags(typeChecker); if (documentation.length > 0) { break; } } } } if (documentation.length === 0 && isIdentifier(location) && symbol.valueDeclaration && isBindingElement(symbol.valueDeclaration)) { const declaration = symbol.valueDeclaration; const parent = declaration.parent; const name = declaration.propertyName || declaration.name; if (isIdentifier(name) && isObjectBindingPattern(parent)) { const propertyName = getTextOfIdentifierOrLiteral(name); const objectType = typeChecker.getTypeAtLocation(parent); documentation = firstDefined(objectType.isUnion() ? objectType.types : [objectType], t => { const prop = t.getProperty(propertyName); return prop ? prop.getDocumentationComment(typeChecker) : undefined; }) || emptyArray; } } if (tags.length === 0 && !hasMultipleSignatures && !isInJSDoc(location)) { tags = symbol.getContextualJsDocTags(enclosingDeclaration, typeChecker); } if (documentation.length === 0 && documentationFromAlias) { documentation = documentationFromAlias; } if (tags.length === 0 && tagsFromAlias) { tags = tagsFromAlias; } const canIncreaseVerbosityLevel = !typeWriterOut.truncated && typeWriterOut.canIncreaseExpansionDepth; return { displayParts, documentation, symbolKind, tags: tags.length === 0 ? undefined : tags, canIncreaseVerbosityLevel: verbosityLevel !== undefined ? canIncreaseVerbosityLevel : undefined, }; function getPrinter() { return createPrinterWithRemoveComments(); } function prefixNextMeaning() { if (displayParts.length) { displayParts.push(lineBreakPart()); } addAliasPrefixIfNecessary(); } function addAliasPrefixIfNecessary() { if (alias) { pushSymbolKind(ScriptElementKind.alias); displayParts.push(spacePart()); } } function addInPrefix() { displayParts.push(spacePart()); displayParts.push(keywordPart(SyntaxKind.InKeyword)); displayParts.push(spacePart()); } function canExpandSymbol(symbol: Symbol, out: WriterContextOut | undefined): boolean { if (verbosityLevel === undefined) { return false; } const type = symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? typeChecker.getDeclaredTypeOfSymbol(symbol) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); if (!type || typeChecker.isLibType(type)) { return false; } if (0 < verbosityLevel) { return true; } if (out) { out.canIncreaseExpansionDepth = true; } return false; } function semanticToSymbolMeaning(meaning: SemanticMeaning): SymbolFlags { let symbolMeaning = SymbolFlags.None; if (meaning & SemanticMeaning.Value) { symbolMeaning |= SymbolFlags.Value; } if (meaning & SemanticMeaning.Type) { symbolMeaning |= SymbolFlags.Type; } if (meaning & SemanticMeaning.Namespace) { symbolMeaning |= SymbolFlags.Namespace; } return symbolMeaning; } /** * Attempts to expand the hover for a symbol, returning true if it succeeded. * e.g. Given a symbol `Foo` corresponding to `class Foo { prop1: number }`, * we will expand the hover to contain a full declaration of the class. */ function tryExpandSymbol(symbol: Symbol, meaning: SemanticMeaning): boolean { // It's possible we call this function multiple times for the same symbol, if the symbol represents more than one kind of thing. // For instance, we'll call this function twice for a symbol that is both a function and a namespace. // In this case, `symbolWasExpanded` will be true the second time we call this function, and we don't need to expand it again. if (symbolWasExpanded) { return true; } if (canExpandSymbol(symbol, typeWriterOut)) { const symbolMeaning = semanticToSymbolMeaning(meaning); const expandedDisplayParts = mapToDisplayParts(writer => { const nodes = typeChecker.getEmitResolver().symbolToDeclarations( symbol, symbolMeaning, TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, maximumLength, verbosityLevel !== undefined ? verbosityLevel - 1 : undefined, typeWriterOut, ); const printer = getPrinter(); const sourceFile = symbol.valueDeclaration && getSourceFileOfNode(symbol.valueDeclaration); nodes.forEach((node, i) => { if (i > 0) writer.writeLine(); printer.writeNode(EmitHint.Unspecified, node, sourceFile, writer); }); }, maximumLength); addRange(displayParts, expandedDisplayParts); symbolWasExpanded = true; return true; } return false; } function addFullSymbolName(symbolToDisplay: Symbol, enclosingDeclaration?: Node) { let indexInfos; if (alias && symbolToDisplay === symbol) { symbolToDisplay = alias; } if (symbolKind === ScriptElementKind.indexSignatureElement) { indexInfos = typeChecker.getIndexInfosOfIndexSymbol(symbolToDisplay); } let fullSymbolDisplayParts: SymbolDisplayPart[] = []; if (symbolToDisplay.flags & SymbolFlags.Signature && indexInfos) { if (symbolToDisplay.parent) { fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay.parent); } fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.OpenBracketToken)); // Needed to handle more than one type of index indexInfos.forEach((info, i) => { // Needed to handle template literals fullSymbolDisplayParts.push(...typeToDisplayParts(typeChecker, info.keyType)); if (i !== indexInfos.length - 1) { fullSymbolDisplayParts.push(spacePart()); fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.BarToken)); fullSymbolDisplayParts.push(spacePart()); } }); fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.CloseBracketToken)); } else { fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay, enclosingDeclaration || sourceFile, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing | SymbolFormatFlags.AllowAnyNodeKind); } addRange(displayParts, fullSymbolDisplayParts); if (symbol.flags & SymbolFlags.Optional) { displayParts.push(punctuationPart(SyntaxKind.QuestionToken)); } } function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) { prefixNextMeaning(); if (symbolKind) { pushSymbolKind(symbolKind); if (symbol && !some(symbol.declarations, d => isArrowFunction(d) || (isFunctionExpression(d) || isClassExpression(d)) && !d.name)) { displayParts.push(spacePart()); addFullSymbolName(symbol); } } } function pushSymbolKind(symbolKind: string) { switch (symbolKind) { case ScriptElementKind.variableElement: case ScriptElementKind.functionElement: case ScriptElementKind.letElement: case ScriptElementKind.constElement: case ScriptElementKind.constructorImplementationElement: case ScriptElementKind.variableUsingElement: case ScriptElementKind.variableAwaitUsingElement: displayParts.push(textOrKeywordPart(symbolKind)); return; default: displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); displayParts.push(textOrKeywordPart(symbolKind)); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); return; } } function addSignatureDisplayParts(signature: Signature, allSignatures: readonly Signature[], flags = TypeFormatFlags.None) { addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature, maximumLength, verbosityLevel, typeWriterOut)); if (allSignatures.length > 1) { displayParts.push(spacePart()); displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); displayParts.push(operatorPart(SyntaxKind.PlusToken)); displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral)); displayParts.push(spacePart()); displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads")); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } documentation = signature.getDocumentationComment(typeChecker); tags = signature.getJsDocTags(); if (allSignatures.length > 1 && documentation.length === 0 && tags.length === 0) { documentation = allSignatures[0].getDocumentationComment(typeChecker); tags = allSignatures[0].getJsDocTags().filter(tag => tag.name !== "deprecated"); // should only include @deprecated JSDoc tag on the first overload (#49368) } } function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined) { const typeParameterParts = mapToDisplayParts(writer => { const params = typeChecker.symbolToTypeParameterDeclarations(symbol, enclosingDeclaration, symbolDisplayNodeBuilderFlags); getPrinter().writeList(ListFormat.TypeParameters, params, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); }); addRange(displayParts, typeParameterParts); } } // TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location /** @internal */ export function getSymbolDisplayPartsDocumentationAndSymbolKind( typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, location: Node, semanticMeaning: SemanticMeaning = getMeaningFromLocation(location), alias?: Symbol, maximumLength?: number, verbosityLevel?: number, ): SymbolDisplayPartsDocumentationAndSymbolKind { return getSymbolDisplayPartsDocumentationAndSymbolKindWorker( typeChecker, symbol, sourceFile, enclosingDeclaration, location, /*type*/ undefined, semanticMeaning, alias, maximumLength, verbosityLevel, ); } function isLocalVariableOrFunction(symbol: Symbol) { if (symbol.parent) { return false; // This is exported symbol } return forEach(symbol.declarations, declaration => { // Function expressions are local if (declaration.kind === SyntaxKind.FunctionExpression) { return true; } if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) { return false; } // If the parent is not sourceFile or module block it is local variable for (let parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) { // Reached source file or module block if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) { return false; } } // parent is in function block return true; }); }