Interactive type inlay hints (#55141)

This commit is contained in:
Maria José Solano
2023-09-19 22:50:11 -07:00
committed by GitHub
parent 1a68590c9c
commit 0b82e1a03f
28 changed files with 1130 additions and 32 deletions
+343 -13
View File
@@ -1,7 +1,10 @@
import {
__String,
ArrayTypeNode,
ArrowFunction,
CallExpression,
ConditionalTypeNode,
ConstructorTypeNode,
createPrinterWithRemoveComments,
createTextSpanFromNode,
Debug,
@@ -16,17 +19,24 @@ import {
FunctionDeclaration,
FunctionExpression,
FunctionLikeDeclaration,
FunctionTypeNode,
GetAccessorDeclaration,
getEffectiveReturnTypeNode,
getEffectiveTypeAnnotationNode,
getLanguageVariant,
getLeadingCommentRanges,
getNameOfDeclaration,
hasContextSensitiveParameters,
Identifier,
idText,
ImportTypeNode,
IndexedAccessTypeNode,
InferTypeNode,
InlayHint,
InlayHintDisplayPart,
InlayHintKind,
InlayHintsContext,
IntersectionTypeNode,
isArrowFunction,
isAssertionExpression,
isBindingPattern,
@@ -52,30 +62,48 @@ import {
isTypeNode,
isVarConst,
isVariableDeclaration,
LiteralTypeNode,
MappedTypeNode,
MethodDeclaration,
NamedTupleMember,
NewExpression,
Node,
NodeArray,
NodeBuilderFlags,
NumericLiteral,
OptionalTypeNode,
ParameterDeclaration,
ParenthesizedTypeNode,
PrefixUnaryExpression,
PropertyDeclaration,
PropertySignature,
QualifiedName,
RestTypeNode,
Signature,
skipParentheses,
some,
StringLiteral,
Symbol,
SymbolFlags,
SyntaxKind,
textSpanIntersectsWith,
tokenToString,
TupleTypeNode,
TupleTypeReference,
Type,
TypeLiteralNode,
TypeOperatorNode,
TypeParameterDeclaration,
TypePredicateNode,
TypeQueryNode,
TypeReferenceNode,
unescapeLeadingUnderscores,
UnionTypeNode,
UserPreferences,
usingSingleLineStringWriter,
VariableDeclaration,
} from "./_namespaces/ts";
const maxTypeHintLength = 30;
const leadingParameterNameCommentRegexFactory = (name: string) => {
return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`);
};
@@ -176,9 +204,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
});
}
function addTypeHints(text: string, position: number) {
function addTypeHints(hintText: string | InlayHintDisplayPart[], position: number) {
result.push({
text: `: ${text.length > maxTypeHintLength ? text.substr(0, maxTypeHintLength - "...".length) + "..." : text}`,
text: typeof hintText === "string" ? `: ${hintText}` : "",
displayParts: typeof hintText === "string" ? undefined : [{ text: ": " }, ...hintText],
position,
kind: InlayHintKind.Type,
whitespaceBefore: true,
@@ -224,13 +253,14 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
return;
}
const typeDisplayString = printTypeInSingleLine(declarationType);
if (typeDisplayString) {
const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), typeDisplayString);
const hints = typeToInlayHintParts(declarationType);
if (hints) {
const hintText = typeof hints === "string" ? hints : hints.map(part => part.text).join("");
const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), hintText);
if (isVariableNameMatchesType) {
return;
}
addTypeHints(typeDisplayString, decl.name.end);
addTypeHints(hints, decl.name.end);
}
}
@@ -355,12 +385,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
return;
}
const typeDisplayString = printTypeInSingleLine(returnType);
if (!typeDisplayString) {
return;
const hint = typeToInlayHintParts(returnType);
if (hint) {
addTypeHints(hint, getTypeAnnotationPosition(decl));
}
addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl));
}
function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) {
@@ -422,6 +450,308 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
});
}
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;
}
switch (node.kind) {
case SyntaxKind.Identifier:
const identifier = node as Identifier;
const identifierText = idText(identifier);
const name = identifier.symbol && identifier.symbol.declarations && identifier.symbol.declarations.length && getNameOfDeclaration(identifier.symbol.declarations[0]);
if (name) {
parts.push(getNodeDisplayPart(identifierText, name));
}
else {
parts.push({ text: identifierText });
}
break;
case SyntaxKind.NumericLiteral:
parts.push({ text: (node as NumericLiteral).text });
break;
case SyntaxKind.StringLiteral:
parts.push({ text: `"${(node as StringLiteral).text}"` });
break;
case SyntaxKind.QualifiedName:
const qualifiedName = node as QualifiedName;
visitForDisplayParts(qualifiedName.left);
parts.push({ text: "." });
visitForDisplayParts(qualifiedName.right);
break;
case SyntaxKind.TypePredicate:
const predicate = node as TypePredicateNode;
if (predicate.assertsModifier) {
parts.push({ text: "asserts " });
}
visitForDisplayParts(predicate.parameterName);
if (predicate.type) {
parts.push({ text: " is " });
visitForDisplayParts(predicate.type);
}
break;
case SyntaxKind.TypeReference:
const typeReference = node as TypeReferenceNode;
visitForDisplayParts(typeReference.typeName);
if (typeReference.typeArguments) {
parts.push({ text: "<" });
visitDisplayPartList(typeReference.typeArguments, ", ");
parts.push({ text: ">" });
}
break;
case SyntaxKind.TypeParameter:
const typeParameter = node as TypeParameterDeclaration;
if (typeParameter.modifiers) {
visitDisplayPartList(typeParameter.modifiers, " ");
}
visitForDisplayParts(typeParameter.name);
if (typeParameter.constraint) {
parts.push({ text: " extends " });
visitForDisplayParts(typeParameter.constraint);
}
if (typeParameter.default) {
parts.push({ text: " = " });
visitForDisplayParts(typeParameter.default);
}
break;
case SyntaxKind.Parameter:
const parameter = node as ParameterDeclaration;
if (parameter.modifiers) {
visitDisplayPartList(parameter.modifiers, " ");
}
if (parameter.dotDotDotToken) {
parts.push({ text: "..." });
}
visitForDisplayParts(parameter.name);
if (parameter.questionToken) {
parts.push({ text: "?" });
}
if (parameter.type) {
parts.push({ text: ": " });
visitForDisplayParts(parameter.type);
}
break;
case SyntaxKind.ConstructorType:
const constructorType = node as ConstructorTypeNode;
parts.push({ text: "new " });
if (constructorType.typeParameters) {
parts.push({ text: "<" });
visitDisplayPartList(constructorType.typeParameters, ", ");
parts.push({ text: ">" });
}
parts.push({ text: "(" });
visitDisplayPartList(constructorType.parameters, ", ");
parts.push({ text: ")" });
parts.push({ text: " => " });
visitForDisplayParts(constructorType.type);
break;
case SyntaxKind.TypeQuery:
const typeQuery = node as TypeQueryNode;
parts.push({ text: "typeof " });
visitForDisplayParts(typeQuery.exprName);
if (typeQuery.typeArguments) {
parts.push({ text: "<" });
visitDisplayPartList(typeQuery.typeArguments, ", ");
parts.push({ text: ">" });
}
break;
case SyntaxKind.TypeLiteral:
const typeLiteral = node as TypeLiteralNode;
parts.push({ text: "{" });
if (typeLiteral.members.length) {
parts.push({ text: " " });
visitDisplayPartList(typeLiteral.members, "; ");
parts.push({ text: " " });
}
parts.push({ text: "}" });
break;
case SyntaxKind.ArrayType:
visitForDisplayParts((node as ArrayTypeNode).elementType);
parts.push({ text: "[]" });
break;
case SyntaxKind.TupleType:
parts.push({ text: "[" });
visitDisplayPartList((node as TupleTypeNode).elements, ", ");
parts.push({ text: "]" });
break;
case SyntaxKind.NamedTupleMember:
const member = node as NamedTupleMember;
if (member.dotDotDotToken) {
parts.push({ text: "..." });
}
visitForDisplayParts(member.name);
if (member.questionToken) {
parts.push({ text: "?" });
}
parts.push({ text: ": " });
visitForDisplayParts(member.type);
break;
case SyntaxKind.OptionalType:
visitForDisplayParts((node as OptionalTypeNode).type);
parts.push({ text: "?" });
break;
case SyntaxKind.RestType:
parts.push({ text: "..." });
visitForDisplayParts((node as RestTypeNode).type);
break;
case SyntaxKind.UnionType:
visitDisplayPartList((node as UnionTypeNode).types, " | ");
break;
case SyntaxKind.IntersectionType:
visitDisplayPartList((node as IntersectionTypeNode).types, " & ");
break;
case SyntaxKind.ConditionalType:
const conditionalType = node as ConditionalTypeNode;
visitForDisplayParts(conditionalType.checkType);
parts.push({ text: " extends " });
visitForDisplayParts(conditionalType.extendsType);
parts.push({ text: " ? " });
visitForDisplayParts(conditionalType.trueType);
parts.push({ text: " : " });
visitForDisplayParts(conditionalType.falseType);
break;
case SyntaxKind.InferType:
parts.push({ text: "infer " });
visitForDisplayParts((node as InferTypeNode).typeParameter);
break;
case SyntaxKind.ParenthesizedType:
parts.push({ text: "(" });
visitForDisplayParts((node as ParenthesizedTypeNode).type);
parts.push({ text: ")" });
break;
case SyntaxKind.TypeOperator:
const typeOperator = node as TypeOperatorNode;
parts.push({ text: `${tokenToString(typeOperator.operator)} ` });
visitForDisplayParts(typeOperator.type);
break;
case SyntaxKind.IndexedAccessType:
const indexedAccess = node as IndexedAccessTypeNode;
visitForDisplayParts(indexedAccess.objectType);
parts.push({ text: "[" });
visitForDisplayParts(indexedAccess.indexType);
parts.push({ text: "]" });
break;
case SyntaxKind.MappedType:
const mappedType = node as MappedTypeNode;
parts.push({ text: "{ " });
if (mappedType.readonlyToken) {
if (mappedType.readonlyToken.kind === SyntaxKind.PlusToken) {
parts.push({ text: "+" });
}
else if (mappedType.readonlyToken.kind === SyntaxKind.MinusToken) {
parts.push({ text: "-" });
}
parts.push({ text: "readonly " });
}
parts.push({ text: "[" });
visitForDisplayParts(mappedType.typeParameter);
if (mappedType.nameType) {
parts.push({ text: " as " });
visitForDisplayParts(mappedType.nameType);
}
parts.push({ text: "]" });
if (mappedType.questionToken) {
if (mappedType.questionToken.kind === SyntaxKind.PlusToken) {
parts.push({ text: "+" });
}
else if (mappedType.questionToken.kind === SyntaxKind.MinusToken) {
parts.push({ text: "-" });
}
parts.push({ text: "?" });
}
parts.push({ text: ": " });
if (mappedType.type) {
visitForDisplayParts(mappedType.type);
}
parts.push({ text: "; }" });
break;
case SyntaxKind.LiteralType:
visitForDisplayParts((node as LiteralTypeNode).literal);
break;
case SyntaxKind.FunctionType:
const functionType = node as FunctionTypeNode;
if (functionType.typeParameters) {
parts.push({ text: "<" });
visitDisplayPartList(functionType.typeParameters, ", ");
parts.push({ text: ">" });
}
parts.push({ text: "(" });
visitDisplayPartList(functionType.parameters, ", ");
parts.push({ text: ")" });
parts.push({ text: " => " });
visitForDisplayParts(functionType.type);
break;
case SyntaxKind.ImportType:
const importType = node as ImportTypeNode;
if (importType.isTypeOf) {
parts.push({ text: "typeof " });
}
parts.push({ text: "import(" });
visitForDisplayParts(importType.argument);
if (importType.assertions) {
parts.push({ text: ", { assert: " });
visitDisplayPartList(importType.assertions.assertClause.elements, ", ");
parts.push({ text: " }" });
}
parts.push({ text: ")" });
if (importType.qualifier) {
parts.push({ text: "." });
visitForDisplayParts(importType.qualifier);
}
if (importType.typeArguments) {
parts.push({ text: "<" });
visitDisplayPartList(importType.typeArguments, ", ");
parts.push({ text: ">" });
}
break;
case SyntaxKind.PropertySignature:
const propertySignature = node as PropertySignature;
if (propertySignature.modifiers) {
visitDisplayPartList(propertySignature.modifiers, " ");
}
visitForDisplayParts(propertySignature.name);
if (propertySignature.questionToken) {
parts.push({ text: "?" });
}
if (propertySignature.type) {
parts.push({ text: ": " });
visitForDisplayParts(propertySignature.type);
}
break;
default:
Debug.failBadSyntaxKind(node);
}
}
function visitDisplayPartList<T extends Node>(nodes: NodeArray<T>, separator: string) {
nodes.forEach((node, index) => {
if (index > 0) {
parts.push({ text: separator });
}
visitForDisplayParts(node);
});
}
}
function isUndefined(name: __String) {
return name === "undefined";
}