@@ -23,24 +23,19 @@ import ts from 'typescript';
23
23
* expandType('typeof Number '); // Outputs:
24
24
* @param {string } type - The type string to be expanded into a structured representation.
25
25
* @todo Share type with expandTypeBabelTS and expandTypeDepFree
26
- * @returns {string | {type: string, [key: string]: any} | undefined } The structured type
26
+ * @returns {string | number | boolean | {type: string, [key: string]: any} | undefined } The structured type
27
27
* representation obtained from parsing and converting the provided type string.
28
28
*/
29
29
function expandType ( type ) {
30
30
const ast = parseType ( type ) ;
31
+ if ( ! ast ) {
32
+ return 'never' ;
33
+ }
31
34
return toSourceTS ( ast ) ;
32
35
}
33
- /**
34
- * @todo I want to use for example: import('typescript').Node
35
- * But the TS types make no sense to me so far ... need to investigate more.
36
- * @typedef TypeScriptType
37
- * @property {object[]|undefined } typeArguments - The type arguments.
38
- * @property {import('typescript').Node } typeName - The type name.
39
- * @property {number } kind - The kind for `ts.SyntaxKind[kind]`.
40
- */
41
36
/**
42
37
* @param {string } str - The type string.
43
- * @returns {TypeScriptType } - The node containing all the information about the input type string.
38
+ * @returns {ts.TypeNode|undefined } - The node containing all the information about the input type string.
44
39
*/
45
40
function parseType ( str ) {
46
41
// TS doesn't like ... notation in this context
@@ -51,7 +46,12 @@ function parseType(str) {
51
46
// type tmp = (...string) => 123; to have a function context
52
47
str = `type tmp = ${ str } ;` ;
53
48
const ast = ts . createSourceFile ( 'repl.ts' , str , ts . ScriptTarget . Latest , true /*setParentNodes*/ ) ;
54
- return ast . statements [ 0 ] . type ;
49
+ const firstStatement = ast . statements [ 0 ] ;
50
+ if ( ! ts . isTypeAliasDeclaration ( firstStatement ) ) {
51
+ console . warn ( 'parseType> Expected type alias declaration, got' , firstStatement , 'instead.' ) ;
52
+ return ;
53
+ }
54
+ return firstStatement . type ;
55
55
}
56
56
/** @type {Record<string, 'missing'|'found'> } */
57
57
export const requiredTypeofs = { } ;
@@ -61,63 +61,116 @@ export const requiredTypeofs = {};
61
61
* This function handles various TypeScript AST node types and converts them into a string
62
62
* or an object representing the type.
63
63
*
64
- * @param {TypeScriptType } node - The TypeScript AST node to convert.
64
+ * @param {ts.TypeNode } node - The TypeScript AST node to convert.
65
65
* @returns {string | number | boolean | {type: string, [key: string]: any} | undefined } The source string/number,
66
66
* or an object with type information based on the node, or `undefined` if the node kind is not handled.
67
67
*/
68
68
function toSourceTS ( node ) {
69
69
const { typeArguments, typeName} = node ;
70
70
const kind_ = ts . SyntaxKind [ node . kind ] ;
71
71
const {
72
- AnyKeyword, ArrayType, BooleanKeyword, FunctionType, Identifier, IntersectionType,
73
- JSDocAllType, LastTypeNode, LiteralType, NullKeyword, NumberKeyword, NumericLiteral,
74
- ObjectKeyword, Parameter, ParenthesizedType, PropertySignature, StringKeyword,
75
- StringLiteral, ThisType, TupleType, TypeLiteral, TypeReference, UndefinedKeyword,
76
- UnionType, JSDocNullableType, TrueKeyword, FalseKeyword, VoidKeyword, UnknownKeyword,
77
- NeverKeyword, BigIntKeyword, BigIntLiteral, ConditionalType, IndexedAccessType, RestType,
78
- TypeQuery, // parseType('typeof Number')
79
- TypeOperator, // parseType('keyof typeof obj')
80
- KeyOfKeyword, // "operator" key in TypeOperator node
81
- ConstructorType, // parseType('new (...args: any[]) => any');
82
- NamedTupleMember,
83
- MappedType, // parseType('{[K in TaskType]: InstanceType<typeof SUPPORTED_TASKS[K]["pipeline"]>}')
84
- TypeParameter, // Basically K and TaskType of MappedType
72
+ AnyKeyword, // parseType('any' ).kind === ts.SyntaxKind.AnyKeyword && toSourceTS(parseType('any')) === 'any'
73
+ ArrayType, // parseType('number[]' ).kind === ts.SyntaxKind.ArrayType // todo toSourceTS(parseType('number[]')) === {type: 'array etc.
74
+ BooleanKeyword, // parseType("boolean" ).kind === ts.SyntaxKind.BooleanKeyword
75
+ FunctionType, // parseType("() => void" ).kind === ts.SyntaxKind.FunctionType
76
+ Identifier, // parseType("{a: 1, b: 2}" ).members[0].name.kind === ts.SyntaxKind.Identifier
77
+ IntersectionType, // parseType("1 & 2" ).kind === ts.SyntaxKind.IntersectionType
78
+ JSDocAllType, // parseType("*" ).kind === ts.SyntaxKind.JSDocAllType
79
+ ImportType, // parseType('import("test").Test' ).kind === ts.SyntaxKind.ImportType
80
+ LiteralType, // parseType("123" ).kind === ts.SyntaxKind.LiteralType
81
+ NullKeyword, // parseType("null" ).literal.kind === ts.SyntaxKind.NullKeyword
82
+ NumberKeyword, // parseType("number" ).kind === ts.SyntaxKind.NumberKeyword
83
+ NumericLiteral, // parseType("123" ).literal.kind === ts.SyntaxKind.NumericLiteral
84
+ ObjectKeyword, // parseType("object" ).kind === ts.SyntaxKind.ObjectKeyword
85
+ Parameter, // parseType("(a) => void" ).parameters[0].kind === ts.SyntaxKind.Parameter
86
+ ParenthesizedType, // parseType("(SomeType)" ).kind === ts.SyntaxKind.ParenthesizedType
87
+ PropertySignature, // parseType("{a: 1, b: 2}" ).members[0].kind === ts.SyntaxKind.PropertySignature
88
+ StringKeyword, // parseType("string" ).kind === ts.SyntaxKind.StringKeyword
89
+ StringLiteral, // parseType("'test'" ).literal.kind === ts.SyntaxKind.StringLiteral
90
+ ThisType, // parseType("this" ).kind === ts.SyntaxKind.ThisType
91
+ TupleType, // parseType("[1, 2, 3]" ).kind === ts.SyntaxKind.TupleType
92
+ TypeLiteral, // parseType("{a: 1, b: 2}" ).kind === ts.SyntaxKind.TypeLiteral
93
+ TypeReference, // parseType("SomeOtherType" ).kind === ts.SyntaxKind.TypeReference
94
+ UndefinedKeyword, // parseType("undefined" ).kind === ts.SyntaxKind.UndefinedKeyword
95
+ UnionType, // parseType("1|2" ).kind === ts.SyntaxKind.UnionType
96
+ JSDocNullableType, // parseType("?lol?" ).kind === ts.SyntaxKind.JSDocNullableType
97
+ TrueKeyword, // parseType("true" ).literal.kind === ts.SyntaxKind.TrueKeyword
98
+ FalseKeyword, // parseType("false" ).literal.kind === ts.SyntaxKind.FalseKeyword
99
+ VoidKeyword, // parseType("void" ).kind === ts.SyntaxKind.VoidKeyword
100
+ UnknownKeyword, // parseType("unknown" ).kind === ts.SyntaxKind.UnknownKeyword
101
+ NeverKeyword, // parseType("never" ).kind === ts.SyntaxKind.NeverKeyword
102
+ BigIntKeyword, // parseType("bigint" ).kind === ts.SyntaxKind.BigIntKeyword
103
+ BigIntLiteral, // parseType("123n" ).literal.kind === ts.SyntaxKind.BigIntLiteral
104
+ ConditionalType, // parseType("1 extends number ? true : false").kind === ts.SyntaxKind.ConditionalType
105
+ IndexedAccessType, // parseType('Test[123]' ).kind === ts.SyntaxKind.IndexedAccessType
106
+ RestType, // parseType("[...number]" ).elements[0].kind === ts.SyntaxKind.RestType
107
+ TypeQuery, // parseType('typeof Number' ).kind === ts.SyntaxKind.TypeQuery
108
+ TypeOperator, // parseType('keyof typeof obj' ).kind === ts.SyntaxKind.TypeOperator
109
+ KeyOfKeyword, // parseType('keyof typeof obj' ).operator === ts.SyntaxKind.KeyOfKeyword
110
+ ConstructorType, // parseType('new (...args: any[]) => any' ).kind === ts.SyntaxKind.ConstructorType
111
+ NamedTupleMember, // parseType('[a: 1]' ).elements[0].kind === ts.SyntaxKind.NamedTupleMember
112
+ MappedType, // parseType('{[K in TaskType]: 123}' ).kind === ts.SyntaxKind.MappedType
113
+ TypeParameter, // parseType('{[K in TaskType]: 123}' ).typeParameter.kind === ts.SyntaxKind.TypeParameter
85
114
} = ts . SyntaxKind ;
86
115
// console.log({typeArguments, typeName, kind_, node});
87
116
switch ( node . kind ) {
88
117
case BigIntKeyword :
89
118
return { type : 'bigint' } ;
90
119
case BigIntLiteral :
120
+ if ( ! ts . isBigIntLiteral ( node ) ) {
121
+ throw Error ( "Impossible" ) ;
122
+ }
91
123
const literal = node . text . slice ( 0 , - 1 ) ; // Remove the "n"
92
124
return { type : 'bigint' , literal} ;
93
125
case ConditionalType :
126
+ if ( ! ts . isConditionalTypeNode ( node ) ) {
127
+ throw Error ( "Impossible" ) ;
128
+ }
94
129
// Keys on node:
95
130
// ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
96
131
// 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
97
- const checkType = toSourceTS ( node . checkType ) ;
132
+ const checkType = toSourceTS ( node . checkType ) ;
98
133
const extendsType = toSourceTS ( node . extendsType ) ;
99
- const trueType = toSourceTS ( node . trueType ) ;
100
- const falseType = toSourceTS ( node . falseType ) ;
134
+ const trueType = toSourceTS ( node . trueType ) ;
135
+ const falseType = toSourceTS ( node . falseType ) ;
101
136
return { type : 'condition' , checkType, extendsType, trueType, falseType} ;
102
137
case ConstructorType : {
138
+ if ( ! ts . isConstructorTypeNode ( node ) ) {
139
+ throw Error ( "Impossible" ) ;
140
+ }
103
141
const parameters = node . parameters . map ( toSourceTS ) ;
104
142
const ret = toSourceTS ( node . type ) ;
105
143
return { type : 'new' , parameters, ret} ;
106
144
}
107
145
case FunctionType :
146
+ if ( ! ts . isFunctionTypeNode ( node ) ) {
147
+ throw Error ( "Impossible" ) ;
148
+ }
108
149
const parameters = node . parameters . map ( toSourceTS ) ;
109
150
return { type : 'function' , parameters} ;
110
151
case IndexedAccessType :
111
- const index = toSourceTS ( node . indexType ) ;
152
+ if ( ! ts . isIndexedAccessTypeNode ( node ) ) {
153
+ throw Error ( "Impossible" ) ;
154
+ }
155
+ const index = toSourceTS ( node . indexType ) ;
112
156
const object = toSourceTS ( node . objectType ) ;
113
157
return { type : 'indexedAccess' , index, object} ;
114
158
case RestType :
159
+ if ( ! ts . isRestTypeNode ( node ) ) {
160
+ throw Error ( "Impossible" ) ;
161
+ }
115
162
const annotation = toSourceTS ( node . type ) ;
116
163
return { type : 'rest' , annotation} ;
117
164
case JSDocNullableType :
165
+ if ( ! ts . isJSDocNullableType ( node ) ) {
166
+ throw Error ( "Impossible" ) ;
167
+ }
118
168
const t = toSourceTS ( node . type ) ;
119
169
return { type : 'union' , members : [ t , 'null' ] } ;
120
170
case MappedType : {
171
+ if ( ! ts . isMappedTypeNode ( node ) ) {
172
+ throw Error ( "Impossible" ) ;
173
+ }
121
174
const result = toSourceTS ( node . type ) ;
122
175
const parameter = node . typeParameter ;
123
176
if ( parameter . kind === TypeParameter ) {
@@ -132,6 +185,9 @@ function toSourceTS(node) {
132
185
// todo work out more: const jsdoc = `(...a: ...number) => 123
133
186
// TS even thinks it's two parameters... just go for array/[]
134
187
case Parameter :
188
+ if ( ! ts . isParameter ( node ) ) {
189
+ throw Error ( "Impossible" ) ;
190
+ }
135
191
const type = node . type ? toSourceTS ( node . type ) : 'any' ;
136
192
const name = toSourceTS ( node . name ) ;
137
193
const ret = { type, name} ;
@@ -140,19 +196,28 @@ function toSourceTS(node) {
140
196
}
141
197
return ret ;
142
198
case TypeQuery :
199
+ if ( ! ts . isTypeQueryNode ( node ) ) {
200
+ throw Error ( "Impossible" ) ;
201
+ }
143
202
const argument = toSourceTS ( node . exprName ) ;
144
203
// Notify Asserter class that we have to register variables with this name
145
204
if ( ! requiredTypeofs [ argument ] ) {
146
205
requiredTypeofs [ argument ] = 'missing' ;
147
206
}
148
207
return { type : 'typeof' , argument} ;
149
208
case TypeOperator :
209
+ if ( ! ts . isTypeOperatorNode ( node ) ) {
210
+ throw Error ( "Impossible" ) ;
211
+ }
150
212
if ( node . operator === KeyOfKeyword ) {
151
213
const argument = toSourceTS ( node . type ) ;
152
214
return { type : 'keyof' , argument} ;
153
215
}
154
216
console . warn ( "unimplemented TypeOperator" , node ) ;
155
217
case TypeReference : {
218
+ if ( ! ts . isTypeReferenceNode ( node ) ) {
219
+ throw Error ( "Impossible" ) ;
220
+ }
156
221
if ( ( typeName . text === 'Object' || typeName . text === 'Record' ) && typeArguments ?. length === 2 ) {
157
222
return {
158
223
type : 'record' ,
@@ -185,23 +250,34 @@ function toSourceTS(node) {
185
250
const args = typeArguments . map ( toSourceTS ) ;
186
251
return { type : 'reference' , name, args} ;
187
252
}
188
- case StringKeyword :
189
- return node . getText ( ) ;
190
- case NumberKeyword :
191
- return node . getText ( ) ;
192
253
case NamedTupleMember :
254
+ if ( ! ts . isNamedTupleMember ( node ) ) {
255
+ throw Error ( "Impossible" ) ;
256
+ }
193
257
return toSourceTS ( node . type ) ;
194
258
case IntersectionType : {
259
+ if ( ! ts . isIntersectionTypeNode ( node ) ) {
260
+ throw Error ( "Impossible" ) ;
261
+ }
195
262
const members = node . types . map ( toSourceTS ) ;
196
263
return { type : 'intersection' , members} ;
197
264
}
198
265
case TupleType :
266
+ if ( ! ts . isTupleTypeNode ( node ) ) {
267
+ throw Error ( "Impossible" ) ;
268
+ }
199
269
const elements = node . elements . map ( toSourceTS ) ;
200
270
return { type : 'tuple' , elements} ;
201
271
case UnionType :
272
+ if ( ! ts . isUnionTypeNode ( node ) ) {
273
+ throw Error ( "Impossible" ) ;
274
+ }
202
275
const members = node . types . map ( toSourceTS ) ;
203
276
return { type : 'union' , members} ;
204
277
case TypeLiteral :
278
+ if ( ! ts . isTypeLiteralNode ( node ) ) {
279
+ throw Error ( "Impossible" ) ;
280
+ }
205
281
const properties = { } ;
206
282
node . members . forEach ( member => {
207
283
const name = toSourceTS ( member . name ) ;
@@ -210,27 +286,41 @@ function toSourceTS(node) {
210
286
} ) ;
211
287
return { type : 'object' , properties} ;
212
288
case PropertySignature :
289
+ if ( ! ts . isPropertySignature ( node ) ) {
290
+ throw Error ( "Impossible" ) ;
291
+ }
213
292
console . warn ( 'toSourceTS> should not happen, handled by TypeLiteral directly' ) ;
214
293
return `${ toSourceTS ( node . name ) } : ${ toSourceTS ( node . type ) } ` ;
215
294
case Identifier :
295
+ if ( ! ts . isIdentifier ( node ) ) {
296
+ throw Error ( "Impossible" ) ;
297
+ }
216
298
return node . text ;
217
299
case ArrayType : {
300
+ if ( ! ts . isArrayTypeNode ( node ) ) {
301
+ throw Error ( "Impossible" ) ;
302
+ }
218
303
const elementType = toSourceTS ( node . elementType ) ;
219
304
return { type : 'array' , elementType} ;
220
305
}
221
306
case LiteralType :
307
+ if ( ! ts . isLiteralTypeNode ( node ) ) {
308
+ throw Error ( "Impossible" ) ;
309
+ }
222
310
return toSourceTS ( node . literal ) ;
223
- case AnyKeyword :
224
- case BooleanKeyword :
311
+ case AnyKeyword :
312
+ case BooleanKeyword :
313
+ case StringKeyword :
314
+ case NeverKeyword :
315
+ case NullKeyword :
316
+ case NumberKeyword :
317
+ case UndefinedKeyword :
318
+ case UnknownKeyword :
319
+ case VoidKeyword :
225
320
// ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
226
321
case JSDocAllType :
227
- case NullKeyword :
322
+ case ThisType :
228
323
case StringLiteral :
229
- case ThisType :
230
- case UndefinedKeyword :
231
- case VoidKeyword :
232
- case UnknownKeyword :
233
- case NeverKeyword :
234
324
return node . getText ( ) ;
235
325
case TrueKeyword :
236
326
return true ;
@@ -244,9 +334,16 @@ function toSourceTS(node) {
244
334
properties : { }
245
335
} ;
246
336
case ParenthesizedType :
337
+ if ( ! ts . isParenthesizedTypeNode ( node ) ) {
338
+ throw Error ( "Impossible" ) ;
339
+ }
247
340
// fall-through for parentheses
248
341
return toSourceTS ( node . type ) ;
249
- case LastTypeNode :
342
+ case ImportType :
343
+ if ( ! ts . isImportTypeNode ( node ) ) {
344
+ throw Error ( "Impossible" ) ;
345
+ }
346
+ /** @todo Handle case without any qualifier like `import('test')` */
250
347
return toSourceTS ( node . qualifier ) ;
251
348
default :
252
349
// const test = {};
0 commit comments