Skip to content

Commit

Permalink
Fill up the visitor switch
Browse files Browse the repository at this point in the history
Handling a bunch of other easy cases (part 2)
  • Loading branch information
MariaSolOs committed Aug 4, 2023
1 parent a63050c commit dec7b99
Showing 1 changed file with 201 additions and 30 deletions.
231 changes: 201 additions & 30 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,13 +59,18 @@ import {
isVarConst,
isVariableDeclaration,
MethodDeclaration,
NamedTupleMember,
NewExpression,
Node,
NodeArray,
NodeBuilderFlags,
OptionalTypeNode,
ParameterDeclaration,
ParenthesizedTypeNode,
PrefixUnaryExpression,
PropertyDeclaration,
QualifiedName,
RestTypeNode,
Signature,
skipParentheses,
some,
Expand All @@ -69,11 +80,17 @@ import {
SyntaxKind,
textSpanIntersectsWith,
tokenToString,
TupleTypeNode,
TupleTypeReference,
Type,
TypeFormatFlags,
TypeNode,
TypeOperatorNode,
TypePredicateNode,
TypeQueryNode,
TypeReferenceNode,
unescapeLeadingUnderscores,
UnionTypeNode,
UserPreferences,
usingSingleLineStringWriter,
VariableDeclaration,
Expand Down Expand Up @@ -162,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 Down Expand Up @@ -225,19 +242,14 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
return;
}

const displayParts = typeToInlayHintDisplayParts(declarationType);
if (displayParts && displayParts.length) {
addTypeHints(displayParts, decl.name.end);
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 @@ -362,18 +374,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
return;
}

const displayParts = typeToInlayHintDisplayParts(returnType);
if (displayParts && displayParts.length) {
addTypeHints(displayParts, getTypeAnnotationPosition(decl));
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 @@ -435,9 +439,9 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
});
}

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

const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
Expand All @@ -446,13 +450,12 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {

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

switch (node.kind) {
// Keyword types:
case SyntaxKind.AnyKeyword:
case SyntaxKind.BigIntKeyword:
case SyntaxKind.BooleanKeyword:
Expand All @@ -465,18 +468,186 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.UnknownKeyword:
case SyntaxKind.VoidKeyword:
parts.push({ text: tokenToString(node.kind) });
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:
// TODO: Make this unreachable when I consider all cases.
return undefined;
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;
}
Expand All @@ -493,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

0 comments on commit dec7b99

Please sign in to comment.