Skip to content

Commit

Permalink
Display parts for types
Browse files Browse the repository at this point in the history
Parenthesized types

Baseline update

Parts for keyword types

Fill up the visitor switch

Handling a bunch of other easy cases (part 2)
  • Loading branch information
MariaSolOs committed Aug 4, 2023
1 parent 01b1821 commit fb19ede
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 18 deletions.
261 changes: 246 additions & 15 deletions src/services/inlayHints.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
__String,
ArrayTypeNode,
ArrowFunction,
CallExpression,
ConditionalTypeNode,
createPrinterWithRemoveComments,
createTextSpanFromNode,
Debug,
Expand All @@ -23,10 +25,14 @@ import {
getLeadingCommentRanges,
hasContextSensitiveParameters,
Identifier,
idText,
ImportTypeNode,
IndexedAccessTypeNode,
InlayHint,
InlayHintDisplayPart,
InlayHintKind,
InlayHintsContext,
IntersectionTypeNode,
isArrowFunction,
isAssertionExpression,
isBindingPattern,
Expand All @@ -53,12 +59,18 @@ import {
isVarConst,
isVariableDeclaration,
MethodDeclaration,
NamedTupleMember,
NewExpression,
Node,
NodeArray,
NodeBuilderFlags,
OptionalTypeNode,
ParameterDeclaration,
ParenthesizedTypeNode,
PrefixUnaryExpression,
PropertyDeclaration,
QualifiedName,
RestTypeNode,
Signature,
skipParentheses,
some,
Expand All @@ -67,17 +79,23 @@ import {
SymbolFlags,
SyntaxKind,
textSpanIntersectsWith,
tokenToString,
TupleTypeNode,
TupleTypeReference,
Type,
TypeFormatFlags,
TypeNode,
TypeOperatorNode,
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?$`);
};
Expand Down Expand Up @@ -161,7 +179,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
function addParameterHints(text: string, parameter: Identifier, position: number, isFirstVariadicArgument: boolean, sourceFile: SourceFile | undefined) {
let hintText: string | InlayHintDisplayPart[] = `${isFirstVariadicArgument ? "..." : ""}${text}`;
if (shouldUseInteractiveInlayHints(preferences)) {
hintText = [getNodeDisplayPart(hintText, parameter, sourceFile!), { text: ":" }];
hintText = [getNodeDisplayPart(hintText, parameter, sourceFile), { text: ":" }];
}
else {
hintText += ":";
Expand All @@ -175,9 +193,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
});
}

function addTypeHints(text: string, position: number) {
function addTypeHints(hintText: string | InlayHintDisplayPart[], position: number) {
const text = typeof hintText === "string" ? `: ${hintText}` : [{ text: ": " }, ...hintText];
result.push({
text: `: ${text.length > maxTypeHintLength ? text.substr(0, maxTypeHintLength - "...".length) + "..." : text}`,
text,
position,
kind: InlayHintKind.Type,
whitespaceBefore: true,
Expand Down Expand Up @@ -223,13 +242,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 hint = typeToInlayHint(declarationType);
if (hint) {
const hintText = typeof hint === "string" ? hint : hint.map(part => part.text).join("");
const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), hintText);
if (isVariableNameMatchesType) {
return;
}
addTypeHints(typeDisplayString, decl.name.end);
addTypeHints(hint, decl.name.end);
}
}

Expand Down Expand Up @@ -354,12 +374,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
return;
}

const typeDisplayString = printTypeInSingleLine(returnType);
if (!typeDisplayString) {
return;
const hint = typeToInlayHint(returnType);
if (hint) {
addTypeHints(hint, getTypeAnnotationPosition(decl));
}

addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl));
}

function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) {
Expand Down Expand Up @@ -421,6 +439,219 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
});
}

function typeToInlayHint(type: Type): InlayHintDisplayPart[] | string {
if (!shouldUseInteractiveInlayHints(preferences)) {
return printTypeInSingleLine(type);
}

const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags);
Debug.assertIsDefined(typeNode, "should always get typenode");

const parts: InlayHintDisplayPart[] = [];
visitor(typeNode);
function visitor(node: Node) {
if (!node) {
return;
}

switch (node.kind) {
case SyntaxKind.AnyKeyword:
case SyntaxKind.BigIntKeyword:
case SyntaxKind.BooleanKeyword:
case SyntaxKind.IntrinsicKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.NumberKeyword:
case SyntaxKind.ObjectKeyword:
case SyntaxKind.StringKeyword:
case SyntaxKind.SymbolKeyword:
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.UnknownKeyword:
case SyntaxKind.VoidKeyword:
case SyntaxKind.ThisType:
parts.push({ text: tokenToString(node.kind)! });
break;
case SyntaxKind.Identifier:
const identifier = node as Identifier;
parts.push(getNodeDisplayPart(idText(identifier), identifier));
break;
case SyntaxKind.QualifiedName:
const qualifiedName = node as QualifiedName;
visitor(qualifiedName.left);
parts.push({ text: "." });
visitor(qualifiedName.right);
break;
case SyntaxKind.TypePredicate:
const predicate = node as TypePredicateNode;
if (predicate.assertsModifier) {
parts.push({ text: "asserts " });
}
visitor(predicate.parameterName);
if (predicate.type) {
parts.push({ text: " is " });
visitor(predicate.type);
}
break;
case SyntaxKind.TypeReference:
const typeReference = node as TypeReferenceNode;
visitor(typeReference.typeName);
if (typeReference.typeArguments) {
parts.push({ text: "<" });
visitList(typeReference.typeArguments, ",");
parts.push({ text: ">" });
}
break;
case SyntaxKind.FunctionType:
// TODO: Handle this case.
break;
case SyntaxKind.ConstructorType:
// TODO: Handle this case.
break;
case SyntaxKind.TypeQuery:
const typeQuery = node as TypeQueryNode;
parts.push({ text: "typeof " });
visitor(typeQuery.exprName);
if (typeQuery.typeArguments) {
parts.push({ text: "<" });
visitList(typeQuery.typeArguments, ",");
parts.push({ text: ">" });
}
break;
case SyntaxKind.TypeLiteral:
// TODO: Handle this case.
break;
case SyntaxKind.ArrayType:
visitor((node as ArrayTypeNode).elementType);
parts.push({ text: "[]" });
break;
case SyntaxKind.TupleType:
parts.push({ text: "[" });
visitList((node as TupleTypeNode).elements, ",");
parts.push({ text: "]" });
break;
case SyntaxKind.NamedTupleMember:
const member = node as NamedTupleMember;
if (member.dotDotDotToken) {
parts.push({ text: "..." });
}
visitor(member.name);
if (member.questionToken) {
parts.push({ text: "?" });
}
parts.push({ text: ": " });
visitor(member.type);
break;
case SyntaxKind.OptionalType:
visitor((node as OptionalTypeNode).type);
parts.push({ text: "?" });
break;
case SyntaxKind.RestType:
parts.push({ text: "..." });
visitor((node as RestTypeNode).type);
break;
case SyntaxKind.UnionType:
visitList((node as UnionTypeNode).types, "|");
break;
case SyntaxKind.IntersectionType:
visitList((node as IntersectionTypeNode).types, "&");
break;
case SyntaxKind.ConditionalType:
const conditionalType = node as ConditionalTypeNode;
visitor(conditionalType.checkType);
parts.push({ text: " extends " });
visitor(conditionalType.extendsType);
parts.push({ text: " ? " });
visitor(conditionalType.trueType);
parts.push({ text: " : " });
visitor(conditionalType.falseType);
break;
case SyntaxKind.InferType:
// TODO: Handle this case.
break;
case SyntaxKind.ParenthesizedType:
parts.push({ text: "(" });
visitor((node as ParenthesizedTypeNode).type);
parts.push({ text: ")" });
break;
case SyntaxKind.TypeOperator:
const typeOperator = node as TypeOperatorNode;
parts.push({ text: `${tokenToString(typeOperator.operator)} ` });
visitor(typeOperator.type);
break;
case SyntaxKind.IndexedAccessType:
const indexedAccess = node as IndexedAccessTypeNode;
visitor(indexedAccess.objectType);
parts.push({ text: "[" });
visitor(indexedAccess.indexType);
parts.push({ text: "]" });
break;
case SyntaxKind.MappedType:
// TODO: Handle this case.
break;
case SyntaxKind.LiteralType:
// TODO: Handle this case.
break;
case SyntaxKind.TemplateLiteralType:
// TODO: Handle this case.
break;
case SyntaxKind.TemplateLiteralTypeSpan:
// TODO: Handle this case.
break;
case SyntaxKind.ImportType:
const importType = node as ImportTypeNode;
if (importType.isTypeOf) {
parts.push({ text: "typeof " });
}
parts.push({ text: "import(" });
visitor(importType.argument);
if (importType.assertions) {
parts.push({ text: ", { assert: " });
// TODO: Visit assert clause entries.
parts.push({ text: " }" });
}
parts.push({ text: ")" });
if (importType.qualifier) {
parts.push({ text: "." });
visitor(importType.qualifier);
}
if (importType.typeArguments) {
parts.push({ text: "<" });
visitList(importType.typeArguments, ",");
parts.push({ text: ">" });
}
break;
case SyntaxKind.ExpressionWithTypeArguments:
// TODO: Handle this case.
break;
// TODO: I _think_ that we don't display inlay hints in JSDocs,
// so I shouldn't worry about these cases (?).
// case SyntaxKind.JSDocTypeExpression:
// case SyntaxKind.JSDocAllType:
// case SyntaxKind.JSDocUnknownType:
// case SyntaxKind.JSDocNonNullableType:
// case SyntaxKind.JSDocNullableType:
// case SyntaxKind.JSDocOptionalType:
// case SyntaxKind.JSDocFunctionType:
// case SyntaxKind.JSDocVariadicType:
// case SyntaxKind.JSDocNamepathType:
// case SyntaxKind.JSDocSignature:
// case SyntaxKind.JSDocTypeLiteral:
default:
Debug.fail("Type node does not support inlay hints.");
}
}
function visitList(nodes: NodeArray<TypeNode>, separator: string) {
nodes.forEach((node, index) => {
if (index > 0) {
parts.push({ text: `${separator} ` });
}
visitor(node);
});
}

return parts;
}

function isUndefined(name: __String) {
return name === "undefined";
}
Expand All @@ -433,7 +664,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
return true;
}

function getNodeDisplayPart(text: string, node: Node, sourceFile: SourceFile): InlayHintDisplayPart {
function getNodeDisplayPart(text: string, node: Node, sourceFile: SourceFile = node.getSourceFile()): InlayHintDisplayPart {
return {
text,
span: createTextSpanFromNode(node, sourceFile),
Expand Down
Loading

0 comments on commit fb19ede

Please sign in to comment.