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,13 +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 ,
60
69
ParenthesizedTypeNode ,
61
70
PrefixUnaryExpression ,
62
71
PropertyDeclaration ,
72
+ QualifiedName ,
73
+ RestTypeNode ,
63
74
Signature ,
64
75
skipParentheses ,
65
76
some ,
@@ -69,11 +80,17 @@ import {
69
80
SyntaxKind ,
70
81
textSpanIntersectsWith ,
71
82
tokenToString ,
83
+ TupleTypeNode ,
72
84
TupleTypeReference ,
73
85
Type ,
74
86
TypeFormatFlags ,
75
87
TypeNode ,
88
+ TypeOperatorNode ,
89
+ TypePredicateNode ,
90
+ TypeQueryNode ,
91
+ TypeReferenceNode ,
76
92
unescapeLeadingUnderscores ,
93
+ UnionTypeNode ,
77
94
UserPreferences ,
78
95
usingSingleLineStringWriter ,
79
96
VariableDeclaration ,
@@ -162,7 +179,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
162
179
function addParameterHints ( text : string , parameter : Identifier , position : number , isFirstVariadicArgument : boolean , sourceFile : SourceFile | undefined ) {
163
180
let hintText : string | InlayHintDisplayPart [ ] = `${ isFirstVariadicArgument ? "..." : "" } ${ text } ` ;
164
181
if ( shouldUseInteractiveInlayHints ( preferences ) ) {
165
- hintText = [ getNodeDisplayPart ( hintText , parameter , sourceFile ! ) , { text : ":" } ] ;
182
+ hintText = [ getNodeDisplayPart ( hintText , parameter , sourceFile ) , { text : ":" } ] ;
166
183
}
167
184
else {
168
185
hintText += ":" ;
@@ -225,19 +242,14 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
225
242
return ;
226
243
}
227
244
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 ) ;
237
249
if ( isVariableNameMatchesType ) {
238
250
return ;
239
251
}
240
- addTypeHints ( typeDisplayString , decl . name . end ) ;
252
+ addTypeHints ( hint , decl . name . end ) ;
241
253
}
242
254
}
243
255
@@ -362,18 +374,10 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
362
374
return ;
363
375
}
364
376
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 ) ) ;
374
380
}
375
-
376
- addTypeHints ( typeDisplayString , getTypeAnnotationPosition ( decl ) ) ;
377
381
}
378
382
379
383
function getTypeAnnotationPosition ( decl : FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration ) {
@@ -435,9 +439,9 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
435
439
} ) ;
436
440
}
437
441
438
- function typeToInlayHintDisplayParts ( type : Type ) : InlayHintDisplayPart [ ] | undefined {
442
+ function typeToInlayHint ( type : Type ) : InlayHintDisplayPart [ ] | string {
439
443
if ( ! shouldUseInteractiveInlayHints ( preferences ) ) {
440
- return undefined ;
444
+ return printTypeInSingleLine ( type ) ;
441
445
}
442
446
443
447
const flags = NodeBuilderFlags . IgnoreErrors | TypeFormatFlags . AllowUniqueESSymbolType | TypeFormatFlags . UseAliasDefinedOutsideCurrentScope ;
@@ -446,13 +450,12 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
446
450
447
451
const parts : InlayHintDisplayPart [ ] = [ ] ;
448
452
visitor ( typeNode ) ;
449
- function visitor ( node : TypeNode ) : true | undefined {
453
+ function visitor ( node : Node ) {
450
454
if ( ! node ) {
451
455
return ;
452
456
}
453
457
454
458
switch ( node . kind ) {
455
- // Keyword types:
456
459
case SyntaxKind . AnyKeyword :
457
460
case SyntaxKind . BigIntKeyword :
458
461
case SyntaxKind . BooleanKeyword :
@@ -465,18 +468,186 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
465
468
case SyntaxKind . UndefinedKeyword :
466
469
case SyntaxKind . UnknownKeyword :
467
470
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.
469
570
break ;
470
571
case SyntaxKind . ParenthesizedType :
471
572
parts . push ( { text : "(" } ) ;
472
573
visitor ( ( node as ParenthesizedTypeNode ) . type ) ;
473
574
parts . push ( { text : ")" } ) ;
474
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:
475
639
default :
476
- // TODO: Make this unreachable when I consider all cases.
477
- return undefined ;
640
+ Debug . fail ( "Type node does not support inlay hints." ) ;
478
641
}
479
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
+ }
480
651
481
652
return parts ;
482
653
}
@@ -493,7 +664,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
493
664
return true ;
494
665
}
495
666
496
- function getNodeDisplayPart ( text : string , node : Node , sourceFile : SourceFile ) : InlayHintDisplayPart {
667
+ function getNodeDisplayPart ( text : string , node : Node , sourceFile : SourceFile = node . getSourceFile ( ) ) : InlayHintDisplayPart {
497
668
return {
498
669
text,
499
670
span : createTextSpanFromNode ( node , sourceFile ) ,
0 commit comments