1
1
import * as ts from "./_namespaces/ts" ;
2
+ import {
3
+ createPrinter ,
4
+ createTextWriter ,
5
+ memoize ,
6
+ } from "./_namespaces/ts" ;
2
7
3
8
export interface TypeWriterTypeResult {
4
9
line : number ;
5
10
syntaxKind : number ;
6
11
sourceText : string ;
7
12
type : string ;
13
+ underline ?: string ;
8
14
}
9
15
10
16
export interface TypeWriterSymbolResult {
@@ -20,6 +26,7 @@ export interface TypeWriterResult {
20
26
sourceText : string ;
21
27
symbol ?: string ;
22
28
type ?: string ;
29
+ underline ?: string ;
23
30
}
24
31
25
32
function * forEachASTNode ( node : ts . Node ) {
@@ -37,6 +44,134 @@ function* forEachASTNode(node: ts.Node) {
37
44
}
38
45
}
39
46
47
+ const createSyntheticNodeUnderliningPrinter = memoize ( ( ) : { printer : ts . Printer ; writer : ts . EmitTextWriter ; underliner : ts . EmitTextWriter ; reset ( ) : void ; } => {
48
+ let underlining = false ;
49
+ const printer = createPrinter ( { removeComments : true } , {
50
+ onEmitNode : ( hint , node , cb ) => {
51
+ if ( ts . nodeIsSynthesized ( node ) !== underlining ) {
52
+ // either node is synthetic and underlining needs to be enabled, or node is not synthetic and
53
+ // underlining needs to be disabled
54
+ underlining = ! underlining ;
55
+ const result = cb ( hint , node ) ;
56
+ underlining = ! underlining ;
57
+ return result ;
58
+ }
59
+ // underlining does not need to change
60
+ return cb ( hint , node ) ;
61
+ } ,
62
+ } ) ;
63
+ const baseWriter = createTextWriter ( "" ) ;
64
+ const underliner = createTextWriter ( "" ) ;
65
+
66
+ return {
67
+ printer,
68
+ writer : {
69
+ write ( s : string ) : void {
70
+ baseWriter . write ( s ) ;
71
+ underliner . write ( underlineFor ( s ) ) ;
72
+ } ,
73
+ writeTrailingSemicolon ( text : string ) : void {
74
+ baseWriter . writeTrailingSemicolon ( text ) ;
75
+ underliner . writeTrailingSemicolon ( underlineFor ( text ) ) ;
76
+ } ,
77
+ writeComment ( text : string ) : void {
78
+ baseWriter . writeComment ( text ) ;
79
+ underliner . writeComment ( underlineFor ( text ) ) ;
80
+ } ,
81
+ getText ( ) : string {
82
+ return baseWriter . getText ( ) ;
83
+ } ,
84
+ rawWrite ( s : string ) : void {
85
+ baseWriter . rawWrite ( s ) ;
86
+ underliner . rawWrite ( underlineFor ( s ) ) ;
87
+ } ,
88
+ writeLiteral ( s : string ) : void {
89
+ baseWriter . writeLiteral ( s ) ;
90
+ underliner . writeLiteral ( underlineFor ( s ) ) ;
91
+ } ,
92
+ getTextPos ( ) : number {
93
+ return baseWriter . getTextPos ( ) ;
94
+ } ,
95
+ getLine ( ) : number {
96
+ return baseWriter . getLine ( ) ;
97
+ } ,
98
+ getColumn ( ) : number {
99
+ return baseWriter . getColumn ( ) ;
100
+ } ,
101
+ getIndent ( ) : number {
102
+ return baseWriter . getIndent ( ) ;
103
+ } ,
104
+ isAtStartOfLine ( ) : boolean {
105
+ return baseWriter . isAtStartOfLine ( ) ;
106
+ } ,
107
+ hasTrailingComment ( ) : boolean {
108
+ return baseWriter . hasTrailingComment ( ) ;
109
+ } ,
110
+ hasTrailingWhitespace ( ) : boolean {
111
+ return baseWriter . hasTrailingWhitespace ( ) ;
112
+ } ,
113
+ writeKeyword ( text : string ) : void {
114
+ baseWriter . writeKeyword ( text ) ;
115
+ underliner . writeKeyword ( underlineFor ( text ) ) ;
116
+ } ,
117
+ writeOperator ( text : string ) : void {
118
+ baseWriter . writeOperator ( text ) ;
119
+ underliner . writeOperator ( underlineFor ( text ) ) ;
120
+ } ,
121
+ writePunctuation ( text : string ) : void {
122
+ baseWriter . writePunctuation ( text ) ;
123
+ underliner . writePunctuation ( underlineFor ( text ) ) ;
124
+ } ,
125
+ writeSpace ( text : string ) : void {
126
+ baseWriter . writeSpace ( text ) ;
127
+ underliner . writeSpace ( underlineFor ( text ) ) ;
128
+ } ,
129
+ writeStringLiteral ( text : string ) : void {
130
+ baseWriter . writeStringLiteral ( text ) ;
131
+ underliner . writeStringLiteral ( underlineFor ( text ) ) ;
132
+ } ,
133
+ writeParameter ( text : string ) : void {
134
+ baseWriter . writeParameter ( text ) ;
135
+ underliner . writeParameter ( underlineFor ( text ) ) ;
136
+ } ,
137
+ writeProperty ( text : string ) : void {
138
+ baseWriter . writeProperty ( text ) ;
139
+ underliner . writeProperty ( underlineFor ( text ) ) ;
140
+ } ,
141
+ writeSymbol ( text : string , symbol : ts . Symbol ) : void {
142
+ baseWriter . writeSymbol ( text , symbol ) ;
143
+ underliner . writeSymbol ( underlineFor ( text ) , symbol ) ;
144
+ } ,
145
+ writeLine ( force ?: boolean | undefined ) : void {
146
+ baseWriter . writeLine ( force ) ;
147
+ underliner . writeLine ( force ) ;
148
+ } ,
149
+ increaseIndent ( ) : void {
150
+ baseWriter . increaseIndent ( ) ;
151
+ underliner . increaseIndent ( ) ;
152
+ } ,
153
+ decreaseIndent ( ) : void {
154
+ baseWriter . decreaseIndent ( ) ;
155
+ underliner . decreaseIndent ( ) ;
156
+ } ,
157
+ clear ( ) : void {
158
+ baseWriter . clear ( ) ;
159
+ underliner . clear ( ) ;
160
+ } ,
161
+ } ,
162
+ underliner,
163
+ reset ( ) {
164
+ underlining = false ;
165
+ baseWriter . clear ( ) ;
166
+ underliner . clear ( ) ;
167
+ } ,
168
+ } ;
169
+
170
+ function underlineFor ( s : string ) {
171
+ return s . length === 0 ? s : ( underlining ? "^" : " " ) . repeat ( s . length ) ;
172
+ }
173
+ } ) ;
174
+
40
175
export class TypeWriterWalker {
41
176
currentSourceFile ! : ts . SourceFile ;
42
177
@@ -123,6 +258,7 @@ export class TypeWriterWalker {
123
258
// return `error`s via `getTypeAtLocation`
124
259
// But this is generally expected, so we don't call those out, either
125
260
let typeString : string ;
261
+ let underline : string | undefined ;
126
262
if (
127
263
! this . hadErrorBaseline &&
128
264
type . flags & ts . TypeFlags . Any &&
@@ -139,18 +275,25 @@ export class TypeWriterWalker {
139
275
}
140
276
else {
141
277
const typeFormatFlags = ts . TypeFormatFlags . NoTruncation | ts . TypeFormatFlags . AllowUniqueESSymbolType | ts . TypeFormatFlags . GenerateNamesForShadowedTypeParams ;
142
- typeString = this . checker . typeToString ( type , node . parent , typeFormatFlags ) ;
143
- if ( ts . isIdentifier ( node ) && ts . isTypeAliasDeclaration ( node . parent ) && node . parent . name === node && typeString === ts . idText ( node ) ) {
278
+ let typeNode = this . checker . typeToTypeNode ( type , node . parent , ( typeFormatFlags & ts . TypeFormatFlags . NodeBuilderFlagsMask ) | ts . NodeBuilderFlags . IgnoreErrors ) ! ;
279
+ if ( ts . isIdentifier ( node ) && ts . isTypeAliasDeclaration ( node . parent ) && node . parent . name === node && ts . isIdentifier ( typeNode ) && ts . idText ( typeNode ) === ts . idText ( node ) ) {
144
280
// for a complex type alias `type T = ...`, showing "T : T" isn't very helpful for type tests. When the type produced is the same as
145
281
// the name of the type alias, recreate the type string without reusing the alias name
146
- typeString = this . checker . typeToString ( type , node . parent , typeFormatFlags | ts . TypeFormatFlags . InTypeAlias ) ;
282
+ typeNode = this . checker . typeToTypeNode ( type , node . parent , ( ( typeFormatFlags | ts . TypeFormatFlags . InTypeAlias ) & ts . TypeFormatFlags . NodeBuilderFlagsMask ) | ts . NodeBuilderFlags . IgnoreErrors ) ! ;
147
283
}
284
+
285
+ const { printer, writer, underliner, reset } = createSyntheticNodeUnderliningPrinter ( ) ;
286
+ printer . writeNode ( ts . EmitHint . Unspecified , typeNode , this . currentSourceFile , writer ) ;
287
+ typeString = writer . getText ( ) ;
288
+ underline = underliner . getText ( ) ;
289
+ reset ( ) ;
148
290
}
149
291
return {
150
292
line : lineAndCharacter . line ,
151
293
syntaxKind : node . kind ,
152
294
sourceText,
153
295
type : typeString ,
296
+ underline,
154
297
} ;
155
298
}
156
299
const symbol = this . checker . getSymbolAtLocation ( node ) ;
0 commit comments