Skip to content

Commit dec7b99

Browse files
committed
Fill up the visitor switch
Handling a bunch of other easy cases (part 2)
1 parent a63050c commit dec7b99

File tree

1 file changed

+201
-30
lines changed

1 file changed

+201
-30
lines changed

src/services/inlayHints.ts

Lines changed: 201 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {
22
__String,
3+
ArrayTypeNode,
34
ArrowFunction,
45
CallExpression,
6+
ConditionalTypeNode,
57
createPrinterWithRemoveComments,
68
createTextSpanFromNode,
79
Debug,
@@ -23,10 +25,14 @@ import {
2325
getLeadingCommentRanges,
2426
hasContextSensitiveParameters,
2527
Identifier,
28+
idText,
29+
ImportTypeNode,
30+
IndexedAccessTypeNode,
2631
InlayHint,
2732
InlayHintDisplayPart,
2833
InlayHintKind,
2934
InlayHintsContext,
35+
IntersectionTypeNode,
3036
isArrowFunction,
3137
isAssertionExpression,
3238
isBindingPattern,
@@ -53,13 +59,18 @@ import {
5359
isVarConst,
5460
isVariableDeclaration,
5561
MethodDeclaration,
62+
NamedTupleMember,
5663
NewExpression,
5764
Node,
65+
NodeArray,
5866
NodeBuilderFlags,
67+
OptionalTypeNode,
5968
ParameterDeclaration,
6069
ParenthesizedTypeNode,
6170
PrefixUnaryExpression,
6271
PropertyDeclaration,
72+
QualifiedName,
73+
RestTypeNode,
6374
Signature,
6475
skipParentheses,
6576
some,
@@ -69,11 +80,17 @@ import {
6980
SyntaxKind,
7081
textSpanIntersectsWith,
7182
tokenToString,
83+
TupleTypeNode,
7284
TupleTypeReference,
7385
Type,
7486
TypeFormatFlags,
7587
TypeNode,
88+
TypeOperatorNode,
89+
TypePredicateNode,
90+
TypeQueryNode,
91+
TypeReferenceNode,
7692
unescapeLeadingUnderscores,
93+
UnionTypeNode,
7794
UserPreferences,
7895
usingSingleLineStringWriter,
7996
VariableDeclaration,
@@ -162,7 +179,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
162179
function addParameterHints(text: string, parameter: Identifier, position: number, isFirstVariadicArgument: boolean, sourceFile: SourceFile | undefined) {
163180
let hintText: string | InlayHintDisplayPart[] = `${isFirstVariadicArgument ? "..." : ""}${text}`;
164181
if (shouldUseInteractiveInlayHints(preferences)) {
165-
hintText = [getNodeDisplayPart(hintText, parameter, sourceFile!), { text: ":" }];
182+
hintText = [getNodeDisplayPart(hintText, parameter, sourceFile), { text: ":" }];
166183
}
167184
else {
168185
hintText += ":";
@@ -225,19 +242,14 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
225242
return;
226243
}
227244

228-
const displayParts = typeToInlayHintDisplayParts(declarationType);
229-
if (displayParts && displayParts.length) {
230-
addTypeHints(displayParts, decl.name.end);
231-
return;
232-
}
233-
234-
const typeDisplayString = printTypeInSingleLine(declarationType);
235-
if (typeDisplayString) {
236-
const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), typeDisplayString);
245+
const hint = typeToInlayHint(declarationType);
246+
if (hint) {
247+
const hintText = typeof hint === "string" ? hint : hint.map(part => part.text).join("");
248+
const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), hintText);
237249
if (isVariableNameMatchesType) {
238250
return;
239251
}
240-
addTypeHints(typeDisplayString, decl.name.end);
252+
addTypeHints(hint, decl.name.end);
241253
}
242254
}
243255

@@ -362,18 +374,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
362374
return;
363375
}
364376

365-
const displayParts = typeToInlayHintDisplayParts(returnType);
366-
if (displayParts && displayParts.length) {
367-
addTypeHints(displayParts, getTypeAnnotationPosition(decl));
368-
return;
369-
}
370-
371-
const typeDisplayString = printTypeInSingleLine(returnType);
372-
if (!typeDisplayString) {
373-
return;
377+
const hint = typeToInlayHint(returnType);
378+
if (hint) {
379+
addTypeHints(hint, getTypeAnnotationPosition(decl));
374380
}
375-
376-
addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl));
377381
}
378382

379383
function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) {
@@ -435,9 +439,9 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
435439
});
436440
}
437441

438-
function typeToInlayHintDisplayParts(type: Type): InlayHintDisplayPart[] | undefined {
442+
function typeToInlayHint(type: Type): InlayHintDisplayPart[] | string {
439443
if (!shouldUseInteractiveInlayHints(preferences)) {
440-
return undefined;
444+
return printTypeInSingleLine(type);
441445
}
442446

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

447451
const parts: InlayHintDisplayPart[] = [];
448452
visitor(typeNode);
449-
function visitor(node: TypeNode): true | undefined {
453+
function visitor(node: Node) {
450454
if (!node) {
451455
return;
452456
}
453457

454458
switch (node.kind) {
455-
// Keyword types:
456459
case SyntaxKind.AnyKeyword:
457460
case SyntaxKind.BigIntKeyword:
458461
case SyntaxKind.BooleanKeyword:
@@ -465,18 +468,186 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
465468
case SyntaxKind.UndefinedKeyword:
466469
case SyntaxKind.UnknownKeyword:
467470
case SyntaxKind.VoidKeyword:
468-
parts.push({ text: tokenToString(node.kind) });
471+
case SyntaxKind.ThisType:
472+
parts.push({ text: tokenToString(node.kind)! });
473+
break;
474+
case SyntaxKind.Identifier:
475+
const identifier = node as Identifier;
476+
parts.push(getNodeDisplayPart(idText(identifier), identifier));
477+
break;
478+
case SyntaxKind.QualifiedName:
479+
const qualifiedName = node as QualifiedName;
480+
visitor(qualifiedName.left);
481+
parts.push({ text: "." });
482+
visitor(qualifiedName.right);
483+
break;
484+
case SyntaxKind.TypePredicate:
485+
const predicate = node as TypePredicateNode;
486+
if (predicate.assertsModifier) {
487+
parts.push({ text: "asserts " });
488+
}
489+
visitor(predicate.parameterName);
490+
if (predicate.type) {
491+
parts.push({ text: " is " });
492+
visitor(predicate.type);
493+
}
494+
break;
495+
case SyntaxKind.TypeReference:
496+
const typeReference = node as TypeReferenceNode;
497+
visitor(typeReference.typeName);
498+
if (typeReference.typeArguments) {
499+
parts.push({ text: "<" });
500+
visitList(typeReference.typeArguments, ",");
501+
parts.push({ text: ">" });
502+
}
503+
break;
504+
case SyntaxKind.FunctionType:
505+
// TODO: Handle this case.
506+
break;
507+
case SyntaxKind.ConstructorType:
508+
// TODO: Handle this case.
509+
break;
510+
case SyntaxKind.TypeQuery:
511+
const typeQuery = node as TypeQueryNode;
512+
parts.push({ text: "typeof " });
513+
visitor(typeQuery.exprName);
514+
if (typeQuery.typeArguments) {
515+
parts.push({ text: "<" });
516+
visitList(typeQuery.typeArguments, ",");
517+
parts.push({ text: ">" });
518+
}
519+
break;
520+
case SyntaxKind.TypeLiteral:
521+
// TODO: Handle this case.
522+
break;
523+
case SyntaxKind.ArrayType:
524+
visitor((node as ArrayTypeNode).elementType);
525+
parts.push({ text: "[]" });
526+
break;
527+
case SyntaxKind.TupleType:
528+
parts.push({ text: "[" });
529+
visitList((node as TupleTypeNode).elements, ",");
530+
parts.push({ text: "]" });
531+
break;
532+
case SyntaxKind.NamedTupleMember:
533+
const member = node as NamedTupleMember;
534+
if (member.dotDotDotToken) {
535+
parts.push({ text: "..." });
536+
}
537+
visitor(member.name);
538+
if (member.questionToken) {
539+
parts.push({ text: "?" });
540+
}
541+
parts.push({ text: ": " });
542+
visitor(member.type);
543+
break;
544+
case SyntaxKind.OptionalType:
545+
visitor((node as OptionalTypeNode).type);
546+
parts.push({ text: "?" });
547+
break;
548+
case SyntaxKind.RestType:
549+
parts.push({ text: "..." });
550+
visitor((node as RestTypeNode).type);
551+
break;
552+
case SyntaxKind.UnionType:
553+
visitList((node as UnionTypeNode).types, "|");
554+
break;
555+
case SyntaxKind.IntersectionType:
556+
visitList((node as IntersectionTypeNode).types, "&");
557+
break;
558+
case SyntaxKind.ConditionalType:
559+
const conditionalType = node as ConditionalTypeNode;
560+
visitor(conditionalType.checkType);
561+
parts.push({ text: " extends " });
562+
visitor(conditionalType.extendsType);
563+
parts.push({ text: " ? " });
564+
visitor(conditionalType.trueType);
565+
parts.push({ text: " : " });
566+
visitor(conditionalType.falseType);
567+
break;
568+
case SyntaxKind.InferType:
569+
// TODO: Handle this case.
469570
break;
470571
case SyntaxKind.ParenthesizedType:
471572
parts.push({ text: "(" });
472573
visitor((node as ParenthesizedTypeNode).type);
473574
parts.push({ text: ")" });
474575
break;
576+
case SyntaxKind.TypeOperator:
577+
const typeOperator = node as TypeOperatorNode;
578+
parts.push({ text: `${tokenToString(typeOperator.operator)} ` });
579+
visitor(typeOperator.type);
580+
break;
581+
case SyntaxKind.IndexedAccessType:
582+
const indexedAccess = node as IndexedAccessTypeNode;
583+
visitor(indexedAccess.objectType);
584+
parts.push({ text: "[" });
585+
visitor(indexedAccess.indexType);
586+
parts.push({ text: "]" });
587+
break;
588+
case SyntaxKind.MappedType:
589+
// TODO: Handle this case.
590+
break;
591+
case SyntaxKind.LiteralType:
592+
// TODO: Handle this case.
593+
break;
594+
case SyntaxKind.TemplateLiteralType:
595+
// TODO: Handle this case.
596+
break;
597+
case SyntaxKind.TemplateLiteralTypeSpan:
598+
// TODO: Handle this case.
599+
break;
600+
case SyntaxKind.ImportType:
601+
const importType = node as ImportTypeNode;
602+
if (importType.isTypeOf) {
603+
parts.push({ text: "typeof " });
604+
}
605+
parts.push({ text: "import(" });
606+
visitor(importType.argument);
607+
if (importType.assertions) {
608+
parts.push({ text: ", { assert: " });
609+
// TODO: Visit assert clause entries.
610+
parts.push({ text: " }" });
611+
}
612+
parts.push({ text: ")" });
613+
if (importType.qualifier) {
614+
parts.push({ text: "." });
615+
visitor(importType.qualifier);
616+
}
617+
if (importType.typeArguments) {
618+
parts.push({ text: "<" });
619+
visitList(importType.typeArguments, ",");
620+
parts.push({ text: ">" });
621+
}
622+
break;
623+
case SyntaxKind.ExpressionWithTypeArguments:
624+
// TODO: Handle this case.
625+
break;
626+
// TODO: I _think_ that we don't display inlay hints in JSDocs,
627+
// so I shouldn't worry about these cases (?).
628+
// case SyntaxKind.JSDocTypeExpression:
629+
// case SyntaxKind.JSDocAllType:
630+
// case SyntaxKind.JSDocUnknownType:
631+
// case SyntaxKind.JSDocNonNullableType:
632+
// case SyntaxKind.JSDocNullableType:
633+
// case SyntaxKind.JSDocOptionalType:
634+
// case SyntaxKind.JSDocFunctionType:
635+
// case SyntaxKind.JSDocVariadicType:
636+
// case SyntaxKind.JSDocNamepathType:
637+
// case SyntaxKind.JSDocSignature:
638+
// case SyntaxKind.JSDocTypeLiteral:
475639
default:
476-
// TODO: Make this unreachable when I consider all cases.
477-
return undefined;
640+
Debug.fail("Type node does not support inlay hints.");
478641
}
479642
}
643+
function visitList(nodes: NodeArray<TypeNode>, separator: string) {
644+
nodes.forEach((node, index) => {
645+
if (index > 0) {
646+
parts.push({ text: `${separator} ` });
647+
}
648+
visitor(node);
649+
});
650+
}
480651

481652
return parts;
482653
}
@@ -493,7 +664,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
493664
return true;
494665
}
495666

496-
function getNodeDisplayPart(text: string, node: Node, sourceFile: SourceFile): InlayHintDisplayPart {
667+
function getNodeDisplayPart(text: string, node: Node, sourceFile: SourceFile = node.getSourceFile()): InlayHintDisplayPart {
497668
return {
498669
text,
499670
span: createTextSpanFromNode(node, sourceFile),

0 commit comments

Comments
 (0)