Skip to content

Commit fb19ede

Browse files
committed
Display parts for types
Parenthesized types Baseline update Parts for keyword types Fill up the visitor switch Handling a bunch of other easy cases (part 2)
1 parent 01b1821 commit fb19ede

File tree

6 files changed

+311
-18
lines changed

6 files changed

+311
-18
lines changed

src/services/inlayHints.ts

+246-15
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,12 +59,18 @@ import {
5359
isVarConst,
5460
isVariableDeclaration,
5561
MethodDeclaration,
62+
NamedTupleMember,
5663
NewExpression,
5764
Node,
65+
NodeArray,
5866
NodeBuilderFlags,
67+
OptionalTypeNode,
5968
ParameterDeclaration,
69+
ParenthesizedTypeNode,
6070
PrefixUnaryExpression,
6171
PropertyDeclaration,
72+
QualifiedName,
73+
RestTypeNode,
6274
Signature,
6375
skipParentheses,
6476
some,
@@ -67,17 +79,23 @@ import {
6779
SymbolFlags,
6880
SyntaxKind,
6981
textSpanIntersectsWith,
82+
tokenToString,
83+
TupleTypeNode,
7084
TupleTypeReference,
7185
Type,
7286
TypeFormatFlags,
87+
TypeNode,
88+
TypeOperatorNode,
89+
TypePredicateNode,
90+
TypeQueryNode,
91+
TypeReferenceNode,
7392
unescapeLeadingUnderscores,
93+
UnionTypeNode,
7494
UserPreferences,
7595
usingSingleLineStringWriter,
7696
VariableDeclaration,
7797
} from "./_namespaces/ts";
7898

79-
const maxTypeHintLength = 30;
80-
8199
const leadingParameterNameCommentRegexFactory = (name: string) => {
82100
return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`);
83101
};
@@ -161,7 +179,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
161179
function addParameterHints(text: string, parameter: Identifier, position: number, isFirstVariadicArgument: boolean, sourceFile: SourceFile | undefined) {
162180
let hintText: string | InlayHintDisplayPart[] = `${isFirstVariadicArgument ? "..." : ""}${text}`;
163181
if (shouldUseInteractiveInlayHints(preferences)) {
164-
hintText = [getNodeDisplayPart(hintText, parameter, sourceFile!), { text: ":" }];
182+
hintText = [getNodeDisplayPart(hintText, parameter, sourceFile), { text: ":" }];
165183
}
166184
else {
167185
hintText += ":";
@@ -175,9 +193,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
175193
});
176194
}
177195

178-
function addTypeHints(text: string, position: number) {
196+
function addTypeHints(hintText: string | InlayHintDisplayPart[], position: number) {
197+
const text = typeof hintText === "string" ? `: ${hintText}` : [{ text: ": " }, ...hintText];
179198
result.push({
180-
text: `: ${text.length > maxTypeHintLength ? text.substr(0, maxTypeHintLength - "...".length) + "..." : text}`,
199+
text,
181200
position,
182201
kind: InlayHintKind.Type,
183202
whitespaceBefore: true,
@@ -223,13 +242,14 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
223242
return;
224243
}
225244

226-
const typeDisplayString = printTypeInSingleLine(declarationType);
227-
if (typeDisplayString) {
228-
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);
229249
if (isVariableNameMatchesType) {
230250
return;
231251
}
232-
addTypeHints(typeDisplayString, decl.name.end);
252+
addTypeHints(hint, decl.name.end);
233253
}
234254
}
235255

@@ -354,12 +374,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
354374
return;
355375
}
356376

357-
const typeDisplayString = printTypeInSingleLine(returnType);
358-
if (!typeDisplayString) {
359-
return;
377+
const hint = typeToInlayHint(returnType);
378+
if (hint) {
379+
addTypeHints(hint, getTypeAnnotationPosition(decl));
360380
}
361-
362-
addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl));
363381
}
364382

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

442+
function typeToInlayHint(type: Type): InlayHintDisplayPart[] | string {
443+
if (!shouldUseInteractiveInlayHints(preferences)) {
444+
return printTypeInSingleLine(type);
445+
}
446+
447+
const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
448+
const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags);
449+
Debug.assertIsDefined(typeNode, "should always get typenode");
450+
451+
const parts: InlayHintDisplayPart[] = [];
452+
visitor(typeNode);
453+
function visitor(node: Node) {
454+
if (!node) {
455+
return;
456+
}
457+
458+
switch (node.kind) {
459+
case SyntaxKind.AnyKeyword:
460+
case SyntaxKind.BigIntKeyword:
461+
case SyntaxKind.BooleanKeyword:
462+
case SyntaxKind.IntrinsicKeyword:
463+
case SyntaxKind.NeverKeyword:
464+
case SyntaxKind.NumberKeyword:
465+
case SyntaxKind.ObjectKeyword:
466+
case SyntaxKind.StringKeyword:
467+
case SyntaxKind.SymbolKeyword:
468+
case SyntaxKind.UndefinedKeyword:
469+
case SyntaxKind.UnknownKeyword:
470+
case SyntaxKind.VoidKeyword:
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.
570+
break;
571+
case SyntaxKind.ParenthesizedType:
572+
parts.push({ text: "(" });
573+
visitor((node as ParenthesizedTypeNode).type);
574+
parts.push({ text: ")" });
575+
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:
639+
default:
640+
Debug.fail("Type node does not support inlay hints.");
641+
}
642+
}
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+
}
651+
652+
return parts;
653+
}
654+
424655
function isUndefined(name: __String) {
425656
return name === "undefined";
426657
}
@@ -433,7 +664,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
433664
return true;
434665
}
435666

436-
function getNodeDisplayPart(text: string, node: Node, sourceFile: SourceFile): InlayHintDisplayPart {
667+
function getNodeDisplayPart(text: string, node: Node, sourceFile: SourceFile = node.getSourceFile()): InlayHintDisplayPart {
437668
return {
438669
text,
439670
span: createTextSpanFromNode(node, sourceFile),

0 commit comments

Comments
 (0)