11import {
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-
8199const 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