Files
TypeScript/src/services/inlayHints.ts
T

918 lines
37 KiB
TypeScript

import {
__String,
ArrowFunction,
CallExpression,
CharacterCodes,
createPrinterWithRemoveComments,
createTextSpanFromNode,
Debug,
ElementFlags,
EmitHint,
EnumMember,
equateStringsCaseInsensitive,
escapeString,
escapeTemplateSubstitution,
Expression,
findChildOfKind,
findIndex,
forEachChild,
FunctionDeclaration,
FunctionExpression,
FunctionLikeDeclaration,
GetAccessorDeclaration,
getEffectiveReturnTypeNode,
getEffectiveTypeAnnotationNode,
getEmitScriptTarget,
getLanguageVariant,
getLeadingCommentRanges,
getNameOfDeclaration,
getQuotePreference,
hasContextSensitiveParameters,
Identifier,
idText,
InlayHint,
InlayHintDisplayPart,
InlayHintKind,
InlayHintsContext,
isArrayBindingPattern,
isArrayTypeNode,
isArrowFunction,
isAssertionExpression,
isBindingElement,
isBindingPattern,
isCallExpression,
isCallSignatureDeclaration,
isConditionalTypeNode,
isConstructorTypeNode,
isEnumMember,
isExpressionWithTypeArguments,
isFunctionDeclaration,
isFunctionExpression,
isFunctionLikeDeclaration,
isFunctionTypeNode,
isGetAccessorDeclaration,
isIdentifier,
isIdentifierText,
isImportTypeNode,
isIndexedAccessTypeNode,
isIndexSignatureDeclaration,
isInferTypeNode,
isInfinityOrNaNString,
isIntersectionTypeNode,
isLiteralExpression,
isLiteralTypeNode,
isMappedTypeNode,
isMethodDeclaration,
isMethodSignature,
isNamedTupleMember,
isNewExpression,
isObjectBindingPattern,
isObjectLiteralExpression,
isOptionalTypeNode,
isParameter,
isParenthesizedTypeNode,
isPartOfParameterDeclaration,
isPrefixUnaryExpression,
isPropertyAccessExpression,
isPropertyDeclaration,
isPropertySignature,
isQualifiedName,
isRestTypeNode,
isSpreadElement,
isTemplateHead,
isTemplateLiteralTypeNode,
isTemplateLiteralTypeSpan,
isTemplateMiddle,
isTemplateTail,
isThisTypeNode,
isTupleTypeNode,
isTypeLiteralNode,
isTypeNode,
isTypeOperatorNode,
isTypeParameterDeclaration,
isTypePredicateNode,
isTypeQueryNode,
isTypeReferenceNode,
isUnionTypeNode,
isVarConst,
isVariableDeclaration,
LiteralLikeNode,
MethodDeclaration,
NewExpression,
Node,
NodeArray,
NodeBuilderFlags,
ParameterDeclaration,
PrefixUnaryExpression,
PropertyDeclaration,
QuotePreference,
Signature,
SignatureDeclarationBase,
skipParentheses,
some,
Symbol,
SymbolFlags,
SyntaxKind,
TemplateLiteralLikeNode,
textSpanIntersectsWith,
tokenToString,
TupleTypeReference,
Type,
TypeFlags,
unescapeLeadingUnderscores,
UserPreferences,
usingSingleLineStringWriter,
VariableDeclaration,
} from "./_namespaces/ts.js";
const leadingParameterNameCommentRegexFactory = (name: string) => {
return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`);
};
function shouldShowParameterNameHints(preferences: UserPreferences) {
return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all";
}
function shouldShowLiteralParameterNameHintsOnly(preferences: UserPreferences) {
return preferences.includeInlayParameterNameHints === "literals";
}
function shouldUseInteractiveInlayHints(preferences: UserPreferences) {
return preferences.interactiveInlayHints === true;
}
/** @internal */
export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
const { file, program, span, cancellationToken, preferences } = context;
const sourceFileText = file.text;
const compilerOptions = program.getCompilerOptions();
const quotePreference = getQuotePreference(file, preferences);
const checker = program.getTypeChecker();
const result: InlayHint[] = [];
visitor(file);
return result;
function visitor(node: Node): true | undefined {
if (!node || node.getFullWidth() === 0) {
return;
}
switch (node.kind) {
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.FunctionExpression:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.ArrowFunction:
cancellationToken.throwIfCancellationRequested();
}
if (!textSpanIntersectsWith(span, node.pos, node.getFullWidth())) {
return;
}
if (isTypeNode(node) && !isExpressionWithTypeArguments(node)) {
return;
}
if (preferences.includeInlayVariableTypeHints && isVariableDeclaration(node)) {
visitVariableLikeDeclaration(node);
}
else if (preferences.includeInlayPropertyDeclarationTypeHints && isPropertyDeclaration(node)) {
visitVariableLikeDeclaration(node);
}
else if (preferences.includeInlayEnumMemberValueHints && isEnumMember(node)) {
visitEnumMember(node);
}
else if (shouldShowParameterNameHints(preferences) && (isCallExpression(node) || isNewExpression(node))) {
visitCallOrNewExpression(node);
}
else {
if (preferences.includeInlayFunctionParameterTypeHints && isFunctionLikeDeclaration(node) && hasContextSensitiveParameters(node)) {
visitFunctionLikeForParameterType(node);
}
if (preferences.includeInlayFunctionLikeReturnTypeHints && isSignatureSupportingReturnAnnotation(node)) {
visitFunctionDeclarationLikeForReturnType(node);
}
}
return forEachChild(node, visitor);
}
function isSignatureSupportingReturnAnnotation(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration {
return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(node) || isGetAccessorDeclaration(node);
}
function addParameterHints(text: string, parameter: Identifier, position: number, isFirstVariadicArgument: boolean) {
let hintText = `${isFirstVariadicArgument ? "..." : ""}${text}`;
let displayParts: InlayHintDisplayPart[] | undefined;
if (shouldUseInteractiveInlayHints(preferences)) {
displayParts = [getNodeDisplayPart(hintText, parameter), { text: ":" }];
hintText = "";
}
else {
hintText += ":";
}
result.push({
text: hintText,
position,
kind: InlayHintKind.Parameter,
whitespaceAfter: true,
displayParts,
});
}
function addTypeHints(hintText: string | InlayHintDisplayPart[], position: number) {
result.push({
text: typeof hintText === "string" ? `: ${hintText}` : "",
displayParts: typeof hintText === "string" ? undefined : [{ text: ": " }, ...hintText],
position,
kind: InlayHintKind.Type,
whitespaceBefore: true,
});
}
function addEnumMemberValueHints(text: string, position: number) {
result.push({
text: `= ${text}`,
position,
kind: InlayHintKind.Enum,
whitespaceBefore: true,
});
}
function visitEnumMember(member: EnumMember) {
if (member.initializer) {
return;
}
const enumValue = checker.getConstantValue(member);
if (enumValue !== undefined) {
addEnumMemberValueHints(enumValue.toString(), member.end);
}
}
function isModuleReferenceType(type: Type) {
return type.symbol && (type.symbol.flags & SymbolFlags.Module);
}
function visitVariableLikeDeclaration(decl: VariableDeclaration | PropertyDeclaration) {
if (
decl.initializer === undefined && !(isPropertyDeclaration(decl) && !(checker.getTypeAtLocation(decl).flags & TypeFlags.Any)) ||
isBindingPattern(decl.name) || (isVariableDeclaration(decl) && !isHintableDeclaration(decl))
) {
return;
}
const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl);
if (effectiveTypeAnnotation) {
return;
}
const declarationType = checker.getTypeAtLocation(decl);
if (isModuleReferenceType(declarationType)) {
return;
}
const hintParts = typeToInlayHintParts(declarationType);
if (hintParts) {
const hintText = typeof hintParts === "string" ? hintParts : hintParts.map(part => part.text).join("");
const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), hintText);
if (isVariableNameMatchesType) {
return;
}
addTypeHints(hintParts, decl.name.end);
}
}
function visitCallOrNewExpression(expr: CallExpression | NewExpression) {
const args = expr.arguments;
if (!args || !args.length) {
return;
}
const candidates: Signature[] = [];
const signature = checker.getResolvedSignatureForSignatureHelp(expr, candidates);
if (!signature || !candidates.length) {
return;
}
let signatureParamPos = 0;
for (const originalArg of args) {
const arg = skipParentheses(originalArg);
if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) {
signatureParamPos++;
continue;
}
let spreadArgs = 0;
if (isSpreadElement(arg)) {
const spreadType = checker.getTypeAtLocation(arg.expression);
if (checker.isTupleType(spreadType)) {
const { elementFlags, fixedLength } = (spreadType as TupleTypeReference).target;
if (fixedLength === 0) {
continue;
}
const firstOptionalIndex = findIndex(elementFlags, f => !(f & ElementFlags.Required));
const requiredArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
if (requiredArgs > 0) {
spreadArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
}
}
}
const identifierInfo = checker.getParameterIdentifierInfoAtPosition(signature, signatureParamPos);
signatureParamPos = signatureParamPos + (spreadArgs || 1);
if (identifierInfo) {
const { parameter, parameterName, isRestParameter: isFirstVariadicArgument } = identifierInfo;
const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName);
if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) {
continue;
}
const name = unescapeLeadingUnderscores(parameterName);
if (leadingCommentsContainsParameterName(arg, name)) {
continue;
}
addParameterHints(name, parameter, originalArg.getStart(), isFirstVariadicArgument);
}
}
}
function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) {
if (isIdentifier(expr)) {
return expr.text === parameterName;
}
if (isPropertyAccessExpression(expr)) {
return expr.name.text === parameterName;
}
return false;
}
function leadingCommentsContainsParameterName(node: Node, name: string) {
if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions), getLanguageVariant(file.scriptKind))) {
return false;
}
const ranges = getLeadingCommentRanges(sourceFileText, node.pos);
if (!ranges?.length) {
return false;
}
const regex = leadingParameterNameCommentRegexFactory(name);
return some(ranges, range => regex.test(sourceFileText.substring(range.pos, range.end)));
}
function isHintableLiteral(node: Node) {
switch (node.kind) {
case SyntaxKind.PrefixUnaryExpression: {
const operand = (node as PrefixUnaryExpression).operand;
return isLiteralExpression(operand) || isIdentifier(operand) && isInfinityOrNaNString(operand.escapedText);
}
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateExpression:
return true;
case SyntaxKind.Identifier: {
const name = (node as Identifier).escapedText;
return isUndefined(name) || isInfinityOrNaNString(name);
}
}
return isLiteralExpression(node);
}
function visitFunctionDeclarationLikeForReturnType(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) {
if (isArrowFunction(decl)) {
if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) {
return;
}
}
const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl);
if (effectiveTypeAnnotation || !decl.body) {
return;
}
const signature = checker.getSignatureFromDeclaration(decl);
if (!signature) {
return;
}
const returnType = checker.getReturnTypeOfSignature(signature);
if (isModuleReferenceType(returnType)) {
return;
}
const hintParts = typeToInlayHintParts(returnType);
if (hintParts) {
addTypeHints(hintParts, getTypeAnnotationPosition(decl));
}
}
function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) {
const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file);
if (closeParenToken) {
return closeParenToken.end;
}
return decl.parameters.end;
}
function visitFunctionLikeForParameterType(node: FunctionLikeDeclaration) {
const signature = checker.getSignatureFromDeclaration(node);
if (!signature) {
return;
}
for (let i = 0; i < node.parameters.length && i < signature.parameters.length; ++i) {
const param = node.parameters[i];
if (!isHintableDeclaration(param)) {
continue;
}
const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param);
if (effectiveTypeAnnotation) {
continue;
}
const typeHints = getParameterDeclarationTypeHints(signature.parameters[i]);
if (!typeHints) {
continue;
}
addTypeHints(typeHints, param.questionToken ? param.questionToken.end : param.name.end);
}
}
function getParameterDeclarationTypeHints(symbol: Symbol) {
const valueDeclaration = symbol.valueDeclaration;
if (!valueDeclaration || !isParameter(valueDeclaration)) {
return undefined;
}
const signatureParamType = checker.getTypeOfSymbolAtLocation(symbol, valueDeclaration);
if (isModuleReferenceType(signatureParamType)) {
return undefined;
}
return typeToInlayHintParts(signatureParamType);
}
function printTypeInSingleLine(type: Type) {
const flags = NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.AllowUniqueESSymbolType | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope;
const printer = createPrinterWithRemoveComments();
return usingSingleLineStringWriter(writer => {
const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags);
Debug.assertIsDefined(typeNode, "should always get typenode");
printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer);
});
}
function typeToInlayHintParts(type: Type): InlayHintDisplayPart[] | string {
if (!shouldUseInteractiveInlayHints(preferences)) {
return printTypeInSingleLine(type);
}
const flags = NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.AllowUniqueESSymbolType | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope;
const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags);
Debug.assertIsDefined(typeNode, "should always get typenode");
const parts: InlayHintDisplayPart[] = [];
visitForDisplayParts(typeNode);
return parts;
function visitForDisplayParts(node: Node) {
if (!node) {
return;
}
const tokenString = tokenToString(node.kind);
if (tokenString) {
parts.push({ text: tokenString });
return;
}
if (isLiteralExpression(node)) {
parts.push({ text: getLiteralText(node) });
return;
}
switch (node.kind) {
case SyntaxKind.Identifier:
Debug.assertNode(node, isIdentifier);
const identifierText = idText(node);
const name = node.symbol && node.symbol.declarations && node.symbol.declarations.length && getNameOfDeclaration(node.symbol.declarations[0]);
if (name) {
parts.push(getNodeDisplayPart(identifierText, name));
}
else {
parts.push({ text: identifierText });
}
break;
case SyntaxKind.QualifiedName:
Debug.assertNode(node, isQualifiedName);
visitForDisplayParts(node.left);
parts.push({ text: "." });
visitForDisplayParts(node.right);
break;
case SyntaxKind.TypePredicate:
Debug.assertNode(node, isTypePredicateNode);
if (node.assertsModifier) {
parts.push({ text: "asserts " });
}
visitForDisplayParts(node.parameterName);
if (node.type) {
parts.push({ text: " is " });
visitForDisplayParts(node.type);
}
break;
case SyntaxKind.TypeReference:
Debug.assertNode(node, isTypeReferenceNode);
visitForDisplayParts(node.typeName);
if (node.typeArguments) {
parts.push({ text: "<" });
visitDisplayPartList(node.typeArguments, ", ");
parts.push({ text: ">" });
}
break;
case SyntaxKind.TypeParameter:
Debug.assertNode(node, isTypeParameterDeclaration);
if (node.modifiers) {
visitDisplayPartList(node.modifiers, " ");
}
visitForDisplayParts(node.name);
if (node.constraint) {
parts.push({ text: " extends " });
visitForDisplayParts(node.constraint);
}
if (node.default) {
parts.push({ text: " = " });
visitForDisplayParts(node.default);
}
break;
case SyntaxKind.Parameter:
Debug.assertNode(node, isParameter);
if (node.modifiers) {
visitDisplayPartList(node.modifiers, " ");
}
if (node.dotDotDotToken) {
parts.push({ text: "..." });
}
visitForDisplayParts(node.name);
if (node.questionToken) {
parts.push({ text: "?" });
}
if (node.type) {
parts.push({ text: ": " });
visitForDisplayParts(node.type);
}
break;
case SyntaxKind.ConstructorType:
Debug.assertNode(node, isConstructorTypeNode);
parts.push({ text: "new " });
visitParametersAndTypeParameters(node);
parts.push({ text: " => " });
visitForDisplayParts(node.type);
break;
case SyntaxKind.TypeQuery:
Debug.assertNode(node, isTypeQueryNode);
parts.push({ text: "typeof " });
visitForDisplayParts(node.exprName);
if (node.typeArguments) {
parts.push({ text: "<" });
visitDisplayPartList(node.typeArguments, ", ");
parts.push({ text: ">" });
}
break;
case SyntaxKind.TypeLiteral:
Debug.assertNode(node, isTypeLiteralNode);
parts.push({ text: "{" });
if (node.members.length) {
parts.push({ text: " " });
visitDisplayPartList(node.members, "; ");
parts.push({ text: " " });
}
parts.push({ text: "}" });
break;
case SyntaxKind.ArrayType:
Debug.assertNode(node, isArrayTypeNode);
visitForDisplayParts(node.elementType);
parts.push({ text: "[]" });
break;
case SyntaxKind.TupleType:
Debug.assertNode(node, isTupleTypeNode);
parts.push({ text: "[" });
visitDisplayPartList(node.elements, ", ");
parts.push({ text: "]" });
break;
case SyntaxKind.NamedTupleMember:
Debug.assertNode(node, isNamedTupleMember);
if (node.dotDotDotToken) {
parts.push({ text: "..." });
}
visitForDisplayParts(node.name);
if (node.questionToken) {
parts.push({ text: "?" });
}
parts.push({ text: ": " });
visitForDisplayParts(node.type);
break;
case SyntaxKind.OptionalType:
Debug.assertNode(node, isOptionalTypeNode);
visitForDisplayParts(node.type);
parts.push({ text: "?" });
break;
case SyntaxKind.RestType:
Debug.assertNode(node, isRestTypeNode);
parts.push({ text: "..." });
visitForDisplayParts(node.type);
break;
case SyntaxKind.UnionType:
Debug.assertNode(node, isUnionTypeNode);
visitDisplayPartList(node.types, " | ");
break;
case SyntaxKind.IntersectionType:
Debug.assertNode(node, isIntersectionTypeNode);
visitDisplayPartList(node.types, " & ");
break;
case SyntaxKind.ConditionalType:
Debug.assertNode(node, isConditionalTypeNode);
visitForDisplayParts(node.checkType);
parts.push({ text: " extends " });
visitForDisplayParts(node.extendsType);
parts.push({ text: " ? " });
visitForDisplayParts(node.trueType);
parts.push({ text: " : " });
visitForDisplayParts(node.falseType);
break;
case SyntaxKind.InferType:
Debug.assertNode(node, isInferTypeNode);
parts.push({ text: "infer " });
visitForDisplayParts(node.typeParameter);
break;
case SyntaxKind.ParenthesizedType:
Debug.assertNode(node, isParenthesizedTypeNode);
parts.push({ text: "(" });
visitForDisplayParts(node.type);
parts.push({ text: ")" });
break;
case SyntaxKind.TypeOperator:
Debug.assertNode(node, isTypeOperatorNode);
parts.push({ text: `${tokenToString(node.operator)} ` });
visitForDisplayParts(node.type);
break;
case SyntaxKind.IndexedAccessType:
Debug.assertNode(node, isIndexedAccessTypeNode);
visitForDisplayParts(node.objectType);
parts.push({ text: "[" });
visitForDisplayParts(node.indexType);
parts.push({ text: "]" });
break;
case SyntaxKind.MappedType:
Debug.assertNode(node, isMappedTypeNode);
parts.push({ text: "{ " });
if (node.readonlyToken) {
if (node.readonlyToken.kind === SyntaxKind.PlusToken) {
parts.push({ text: "+" });
}
else if (node.readonlyToken.kind === SyntaxKind.MinusToken) {
parts.push({ text: "-" });
}
parts.push({ text: "readonly " });
}
parts.push({ text: "[" });
visitForDisplayParts(node.typeParameter);
if (node.nameType) {
parts.push({ text: " as " });
visitForDisplayParts(node.nameType);
}
parts.push({ text: "]" });
if (node.questionToken) {
if (node.questionToken.kind === SyntaxKind.PlusToken) {
parts.push({ text: "+" });
}
else if (node.questionToken.kind === SyntaxKind.MinusToken) {
parts.push({ text: "-" });
}
parts.push({ text: "?" });
}
parts.push({ text: ": " });
if (node.type) {
visitForDisplayParts(node.type);
}
parts.push({ text: "; }" });
break;
case SyntaxKind.LiteralType:
Debug.assertNode(node, isLiteralTypeNode);
visitForDisplayParts(node.literal);
break;
case SyntaxKind.FunctionType:
Debug.assertNode(node, isFunctionTypeNode);
visitParametersAndTypeParameters(node);
parts.push({ text: " => " });
visitForDisplayParts(node.type);
break;
case SyntaxKind.ImportType:
Debug.assertNode(node, isImportTypeNode);
if (node.isTypeOf) {
parts.push({ text: "typeof " });
}
parts.push({ text: "import(" });
visitForDisplayParts(node.argument);
if (node.assertions) {
parts.push({ text: ", { assert: " });
visitDisplayPartList(node.assertions.assertClause.elements, ", ");
parts.push({ text: " }" });
}
parts.push({ text: ")" });
if (node.qualifier) {
parts.push({ text: "." });
visitForDisplayParts(node.qualifier);
}
if (node.typeArguments) {
parts.push({ text: "<" });
visitDisplayPartList(node.typeArguments, ", ");
parts.push({ text: ">" });
}
break;
case SyntaxKind.PropertySignature:
Debug.assertNode(node, isPropertySignature);
if (node.modifiers?.length) {
visitDisplayPartList(node.modifiers, " ");
parts.push({ text: " " });
}
visitForDisplayParts(node.name);
if (node.questionToken) {
parts.push({ text: "?" });
}
if (node.type) {
parts.push({ text: ": " });
visitForDisplayParts(node.type);
}
break;
case SyntaxKind.IndexSignature:
Debug.assertNode(node, isIndexSignatureDeclaration);
parts.push({ text: "[" });
visitDisplayPartList(node.parameters, ", ");
parts.push({ text: "]" });
if (node.type) {
parts.push({ text: ": " });
visitForDisplayParts(node.type);
}
break;
case SyntaxKind.MethodSignature:
Debug.assertNode(node, isMethodSignature);
if (node.modifiers?.length) {
visitDisplayPartList(node.modifiers, " ");
parts.push({ text: " " });
}
visitForDisplayParts(node.name);
if (node.questionToken) {
parts.push({ text: "?" });
}
visitParametersAndTypeParameters(node);
if (node.type) {
parts.push({ text: ": " });
visitForDisplayParts(node.type);
}
break;
case SyntaxKind.CallSignature:
Debug.assertNode(node, isCallSignatureDeclaration);
visitParametersAndTypeParameters(node);
if (node.type) {
parts.push({ text: ": " });
visitForDisplayParts(node.type);
}
break;
case SyntaxKind.ArrayBindingPattern:
Debug.assertNode(node, isArrayBindingPattern);
parts.push({ text: "[" });
visitDisplayPartList(node.elements, ", ");
parts.push({ text: "]" });
break;
case SyntaxKind.ObjectBindingPattern:
Debug.assertNode(node, isObjectBindingPattern);
parts.push({ text: "{" });
if (node.elements.length) {
parts.push({ text: " " });
visitDisplayPartList(node.elements, ", ");
parts.push({ text: " " });
}
parts.push({ text: "}" });
break;
case SyntaxKind.BindingElement:
Debug.assertNode(node, isBindingElement);
visitForDisplayParts(node.name);
break;
case SyntaxKind.PrefixUnaryExpression:
Debug.assertNode(node, isPrefixUnaryExpression);
parts.push({ text: tokenToString(node.operator) });
visitForDisplayParts(node.operand);
break;
case SyntaxKind.TemplateLiteralType:
Debug.assertNode(node, isTemplateLiteralTypeNode);
visitForDisplayParts(node.head);
node.templateSpans.forEach(visitForDisplayParts);
break;
case SyntaxKind.TemplateHead:
Debug.assertNode(node, isTemplateHead);
parts.push({ text: getLiteralText(node) });
break;
case SyntaxKind.TemplateLiteralTypeSpan:
Debug.assertNode(node, isTemplateLiteralTypeSpan);
visitForDisplayParts(node.type);
visitForDisplayParts(node.literal);
break;
case SyntaxKind.TemplateMiddle:
Debug.assertNode(node, isTemplateMiddle);
parts.push({ text: getLiteralText(node) });
break;
case SyntaxKind.TemplateTail:
Debug.assertNode(node, isTemplateTail);
parts.push({ text: getLiteralText(node) });
break;
case SyntaxKind.ThisType:
Debug.assertNode(node, isThisTypeNode);
parts.push({ text: "this" });
break;
default:
Debug.failBadSyntaxKind(node);
}
}
/**
* Visits the type parameters and parameters, returning something like:
* <T1, T2>(p1: t1, p2: t2)
* which can be used for signature declaration nodes.
* @param signatureDeclaration Node to visit.
*/
function visitParametersAndTypeParameters(signatureDeclaration: SignatureDeclarationBase) {
if (signatureDeclaration.typeParameters) {
parts.push({ text: "<" });
visitDisplayPartList(signatureDeclaration.typeParameters, ", ");
parts.push({ text: ">" });
}
parts.push({ text: "(" });
visitDisplayPartList(signatureDeclaration.parameters, ", ");
parts.push({ text: ")" });
}
function visitDisplayPartList<T extends Node>(nodes: NodeArray<T>, separator: string) {
nodes.forEach((node, index) => {
if (index > 0) {
parts.push({ text: separator });
}
visitForDisplayParts(node);
});
}
function getLiteralText(node: LiteralLikeNode) {
switch (node.kind) {
case SyntaxKind.StringLiteral:
return quotePreference === QuotePreference.Single ? `'${escapeString(node.text, CharacterCodes.singleQuote)}'` : `"${escapeString(node.text, CharacterCodes.doubleQuote)}"`;
case SyntaxKind.TemplateHead:
case SyntaxKind.TemplateMiddle:
case SyntaxKind.TemplateTail: {
const rawText = (node as TemplateLiteralLikeNode).rawText ?? escapeTemplateSubstitution(escapeString(node.text, CharacterCodes.backtick));
switch (node.kind) {
case SyntaxKind.TemplateHead:
return "`" + rawText + "${";
case SyntaxKind.TemplateMiddle:
return "}" + rawText + "${";
case SyntaxKind.TemplateTail:
return "}" + rawText + "`";
}
}
}
return node.text;
}
}
function isUndefined(name: __String) {
return name === "undefined";
}
function isHintableDeclaration(node: VariableDeclaration | ParameterDeclaration) {
if ((isPartOfParameterDeclaration(node) || isVariableDeclaration(node) && isVarConst(node)) && node.initializer) {
const initializer = skipParentheses(node.initializer);
return !(isHintableLiteral(initializer) || isNewExpression(initializer) || isObjectLiteralExpression(initializer) || isAssertionExpression(initializer));
}
return true;
}
function getNodeDisplayPart(text: string, node: Node): InlayHintDisplayPart {
const sourceFile = node.getSourceFile();
return {
text,
span: createTextSpanFromNode(node, sourceFile),
file: sourceFile.fileName,
};
}
}