1
1
import {
2
2
__String ,
3
+ ArrayTypeNode ,
3
4
ArrowFunction ,
4
5
CallExpression ,
6
+ ConditionalTypeNode ,
5
7
createPrinterWithRemoveComments ,
6
8
createTextSpanFromNode ,
7
9
Debug ,
@@ -23,10 +25,14 @@ import {
23
25
getLeadingCommentRanges ,
24
26
hasContextSensitiveParameters ,
25
27
Identifier ,
28
+ idText ,
29
+ ImportTypeNode ,
30
+ IndexedAccessTypeNode ,
26
31
InlayHint ,
27
32
InlayHintDisplayPart ,
28
33
InlayHintKind ,
29
34
InlayHintsContext ,
35
+ IntersectionTypeNode ,
30
36
isArrowFunction ,
31
37
isAssertionExpression ,
32
38
isBindingPattern ,
@@ -53,12 +59,18 @@ import {
53
59
isVarConst ,
54
60
isVariableDeclaration ,
55
61
MethodDeclaration ,
62
+ NamedTupleMember ,
56
63
NewExpression ,
57
64
Node ,
65
+ NodeArray ,
58
66
NodeBuilderFlags ,
67
+ OptionalTypeNode ,
59
68
ParameterDeclaration ,
69
+ ParenthesizedTypeNode ,
60
70
PrefixUnaryExpression ,
61
71
PropertyDeclaration ,
72
+ QualifiedName ,
73
+ RestTypeNode ,
62
74
Signature ,
63
75
skipParentheses ,
64
76
some ,
@@ -67,17 +79,23 @@ import {
67
79
SymbolFlags ,
68
80
SyntaxKind ,
69
81
textSpanIntersectsWith ,
82
+ tokenToString ,
83
+ TupleTypeNode ,
70
84
TupleTypeReference ,
71
85
Type ,
72
86
TypeFormatFlags ,
87
+ TypeNode ,
88
+ TypeOperatorNode ,
89
+ TypePredicateNode ,
90
+ TypeQueryNode ,
91
+ TypeReferenceNode ,
73
92
unescapeLeadingUnderscores ,
93
+ UnionTypeNode ,
74
94
UserPreferences ,
75
95
usingSingleLineStringWriter ,
76
96
VariableDeclaration ,
77
97
} from "./_namespaces/ts" ;
78
98
79
- const maxTypeHintLength = 30 ;
80
-
81
99
const leadingParameterNameCommentRegexFactory = ( name : string ) => {
82
100
return new RegExp ( `^\\s?/\\*\\*?\\s?${ name } \\s?\\*\\/\\s?$` ) ;
83
101
} ;
@@ -161,7 +179,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
161
179
function addParameterHints ( text : string , parameter : Identifier , position : number , isFirstVariadicArgument : boolean , sourceFile : SourceFile | undefined ) {
162
180
let hintText : string | InlayHintDisplayPart [ ] = `${ isFirstVariadicArgument ? "..." : "" } ${ text } ` ;
163
181
if ( shouldUseInteractiveInlayHints ( preferences ) ) {
164
- hintText = [ getNodeDisplayPart ( hintText , parameter , sourceFile ! ) , { text : ":" } ] ;
182
+ hintText = [ getNodeDisplayPart ( hintText , parameter , sourceFile ) , { text : ":" } ] ;
165
183
}
166
184
else {
167
185
hintText += ":" ;
@@ -175,9 +193,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
175
193
} ) ;
176
194
}
177
195
178
- function addTypeHints ( text : string , position : number ) {
196
+ function addTypeHints ( hintText : string | InlayHintDisplayPart [ ] , position : number ) {
197
+ const text = typeof hintText === "string" ? `: ${ hintText } ` : [ { text : ": " } , ...hintText ] ;
179
198
result . push ( {
180
- text : `: ${ text . length > maxTypeHintLength ? text . substr ( 0 , maxTypeHintLength - "..." . length ) + "..." : text } ` ,
199
+ text,
181
200
position,
182
201
kind : InlayHintKind . Type ,
183
202
whitespaceBefore : true ,
@@ -223,13 +242,14 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
223
242
return ;
224
243
}
225
244
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 ) ;
229
249
if ( isVariableNameMatchesType ) {
230
250
return ;
231
251
}
232
- addTypeHints ( typeDisplayString , decl . name . end ) ;
252
+ addTypeHints ( hint , decl . name . end ) ;
233
253
}
234
254
}
235
255
@@ -354,12 +374,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
354
374
return ;
355
375
}
356
376
357
- const typeDisplayString = printTypeInSingleLine ( returnType ) ;
358
- if ( ! typeDisplayString ) {
359
- return ;
377
+ const hint = typeToInlayHint ( returnType ) ;
378
+ if ( hint ) {
379
+ addTypeHints ( hint , getTypeAnnotationPosition ( decl ) ) ;
360
380
}
361
-
362
- addTypeHints ( typeDisplayString , getTypeAnnotationPosition ( decl ) ) ;
363
381
}
364
382
365
383
function getTypeAnnotationPosition ( decl : FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration ) {
@@ -421,6 +439,219 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
421
439
} ) ;
422
440
}
423
441
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
+
424
655
function isUndefined ( name : __String ) {
425
656
return name === "undefined" ;
426
657
}
@@ -433,7 +664,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
433
664
return true ;
434
665
}
435
666
436
- function getNodeDisplayPart ( text : string , node : Node , sourceFile : SourceFile ) : InlayHintDisplayPart {
667
+ function getNodeDisplayPart ( text : string , node : Node , sourceFile : SourceFile = node . getSourceFile ( ) ) : InlayHintDisplayPart {
437
668
return {
438
669
text,
439
670
span : createTextSpanFromNode ( node , sourceFile ) ,
0 commit comments