@runtime-type-inspector/transpiler 3.2.5 → 3.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +299 -109
- package/index.mjs +325 -109
- package/package.json +2 -2
package/index.mjs
CHANGED
|
@@ -25,24 +25,19 @@ import ts from 'typescript';
|
|
|
25
25
|
* expandType('typeof Number '); // Outputs:
|
|
26
26
|
* @param {string} type - The type string to be expanded into a structured representation.
|
|
27
27
|
* @todo Share type with expandTypeBabelTS and expandTypeDepFree
|
|
28
|
-
* @returns {string | {type: string, [key: string]: any} | undefined} The structured type
|
|
28
|
+
* @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The structured type
|
|
29
29
|
* representation obtained from parsing and converting the provided type string.
|
|
30
30
|
*/
|
|
31
31
|
function expandType(type) {
|
|
32
32
|
const ast = parseType(type);
|
|
33
|
+
if (!ast) {
|
|
34
|
+
return 'never';
|
|
35
|
+
}
|
|
33
36
|
return toSourceTS(ast);
|
|
34
37
|
}
|
|
35
|
-
/**
|
|
36
|
-
* @todo I want to use for example: import('typescript').Node
|
|
37
|
-
* But the TS types make no sense to me so far ... need to investigate more.
|
|
38
|
-
* @typedef TypeScriptType
|
|
39
|
-
* @property {object[]|undefined} typeArguments - The type arguments.
|
|
40
|
-
* @property {import('typescript').Node} typeName - The type name.
|
|
41
|
-
* @property {number} kind - The kind for `ts.SyntaxKind[kind]`.
|
|
42
|
-
*/
|
|
43
38
|
/**
|
|
44
39
|
* @param {string} str - The type string.
|
|
45
|
-
* @returns {
|
|
40
|
+
* @returns {ts.TypeNode|undefined} - The node containing all the information about the input type string.
|
|
46
41
|
*/
|
|
47
42
|
function parseType(str) {
|
|
48
43
|
// TS doesn't like ... notation in this context
|
|
@@ -53,7 +48,12 @@ function parseType(str) {
|
|
|
53
48
|
// type tmp = (...string) => 123; to have a function context
|
|
54
49
|
str = `type tmp = ${str};`;
|
|
55
50
|
const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, true /*setParentNodes*/);
|
|
56
|
-
|
|
51
|
+
const firstStatement = ast.statements[0];
|
|
52
|
+
if (!ts.isTypeAliasDeclaration(firstStatement)) {
|
|
53
|
+
console.warn('parseType> Expected type alias declaration, got', firstStatement, 'instead.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
return firstStatement.type;
|
|
57
57
|
}
|
|
58
58
|
/** @type {Record<string, 'missing'|'found'>} */
|
|
59
59
|
const requiredTypeofs = {};
|
|
@@ -63,7 +63,7 @@ const requiredTypeofs = {};
|
|
|
63
63
|
* This function handles various TypeScript AST node types and converts them into a string
|
|
64
64
|
* or an object representing the type.
|
|
65
65
|
*
|
|
66
|
-
* @param {
|
|
66
|
+
* @param {ts.TypeNode|ts.Identifier} node - The TypeScript AST node to convert.
|
|
67
67
|
* @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The source string/number,
|
|
68
68
|
* or an object with type information based on the node, or `undefined` if the node kind is not handled.
|
|
69
69
|
*/
|
|
@@ -75,52 +75,90 @@ function toSourceTS(node) {
|
|
|
75
75
|
const kind_ = ts.SyntaxKind[node.kind];
|
|
76
76
|
const {
|
|
77
77
|
AnyKeyword,
|
|
78
|
+
// parseType('any' ).kind === ts.SyntaxKind.AnyKeyword && toSourceTS(parseType('any')) === 'any'
|
|
78
79
|
ArrayType,
|
|
80
|
+
// parseType('number[]' ).kind === ts.SyntaxKind.ArrayType // todo toSourceTS(parseType('number[]')) === {type: 'array etc.
|
|
79
81
|
BooleanKeyword,
|
|
82
|
+
// parseType("boolean" ).kind === ts.SyntaxKind.BooleanKeyword
|
|
80
83
|
FunctionType,
|
|
84
|
+
// parseType("() => void" ).kind === ts.SyntaxKind.FunctionType
|
|
81
85
|
Identifier,
|
|
86
|
+
// parseType("{a: 1, b: 2}" ).members[0].name.kind === ts.SyntaxKind.Identifier
|
|
82
87
|
IntersectionType,
|
|
88
|
+
// parseType("1 & 2" ).kind === ts.SyntaxKind.IntersectionType
|
|
83
89
|
JSDocAllType,
|
|
84
|
-
|
|
90
|
+
// parseType("*" ).kind === ts.SyntaxKind.JSDocAllType
|
|
91
|
+
ImportType,
|
|
92
|
+
// parseType('import("test").Test' ).kind === ts.SyntaxKind.ImportType
|
|
85
93
|
LiteralType,
|
|
94
|
+
// parseType("123" ).kind === ts.SyntaxKind.LiteralType
|
|
86
95
|
NullKeyword,
|
|
96
|
+
// parseType("null" ).literal.kind === ts.SyntaxKind.NullKeyword
|
|
87
97
|
NumberKeyword,
|
|
98
|
+
// parseType("number" ).kind === ts.SyntaxKind.NumberKeyword
|
|
88
99
|
NumericLiteral,
|
|
100
|
+
// parseType("123" ).literal.kind === ts.SyntaxKind.NumericLiteral
|
|
89
101
|
ObjectKeyword,
|
|
102
|
+
// parseType("object" ).kind === ts.SyntaxKind.ObjectKeyword
|
|
90
103
|
Parameter,
|
|
104
|
+
// parseType("(a) => void" ).parameters[0].kind === ts.SyntaxKind.Parameter
|
|
91
105
|
ParenthesizedType,
|
|
106
|
+
// parseType("(SomeType)" ).kind === ts.SyntaxKind.ParenthesizedType
|
|
92
107
|
PropertySignature,
|
|
108
|
+
// parseType("{a: 1, b: 2}" ).members[0].kind === ts.SyntaxKind.PropertySignature
|
|
93
109
|
StringKeyword,
|
|
110
|
+
// parseType("string" ).kind === ts.SyntaxKind.StringKeyword
|
|
94
111
|
StringLiteral,
|
|
112
|
+
// parseType("'test'" ).literal.kind === ts.SyntaxKind.StringLiteral
|
|
95
113
|
ThisType,
|
|
114
|
+
// parseType("this" ).kind === ts.SyntaxKind.ThisType
|
|
96
115
|
TupleType,
|
|
116
|
+
// parseType("[1, 2, 3]" ).kind === ts.SyntaxKind.TupleType
|
|
97
117
|
TypeLiteral,
|
|
118
|
+
// parseType("{a: 1, b: 2}" ).kind === ts.SyntaxKind.TypeLiteral
|
|
98
119
|
TypeReference,
|
|
120
|
+
// parseType("SomeOtherType" ).kind === ts.SyntaxKind.TypeReference
|
|
99
121
|
UndefinedKeyword,
|
|
122
|
+
// parseType("undefined" ).kind === ts.SyntaxKind.UndefinedKeyword
|
|
100
123
|
UnionType,
|
|
124
|
+
// parseType("1|2" ).kind === ts.SyntaxKind.UnionType
|
|
101
125
|
JSDocNullableType,
|
|
126
|
+
// parseType("?lol?" ).kind === ts.SyntaxKind.JSDocNullableType
|
|
102
127
|
TrueKeyword,
|
|
128
|
+
// parseType("true" ).literal.kind === ts.SyntaxKind.TrueKeyword
|
|
103
129
|
FalseKeyword,
|
|
130
|
+
// parseType("false" ).literal.kind === ts.SyntaxKind.FalseKeyword
|
|
104
131
|
VoidKeyword,
|
|
132
|
+
// parseType("void" ).kind === ts.SyntaxKind.VoidKeyword
|
|
105
133
|
UnknownKeyword,
|
|
134
|
+
// parseType("unknown" ).kind === ts.SyntaxKind.UnknownKeyword
|
|
106
135
|
NeverKeyword,
|
|
136
|
+
// parseType("never" ).kind === ts.SyntaxKind.NeverKeyword
|
|
107
137
|
BigIntKeyword,
|
|
138
|
+
// parseType("bigint" ).kind === ts.SyntaxKind.BigIntKeyword
|
|
108
139
|
BigIntLiteral,
|
|
140
|
+
// parseType("123n" ).literal.kind === ts.SyntaxKind.BigIntLiteral
|
|
109
141
|
ConditionalType,
|
|
142
|
+
// parseType("1 extends number ? true : false").kind === ts.SyntaxKind.ConditionalType
|
|
110
143
|
IndexedAccessType,
|
|
144
|
+
// parseType('Test[123]' ).kind === ts.SyntaxKind.IndexedAccessType
|
|
145
|
+
IndexSignature,
|
|
146
|
+
// parseType('{[n: number]: string}' ).members[0].kind === ts.SyntaxKind.IndexSignature
|
|
111
147
|
RestType,
|
|
148
|
+
// parseType("[...number]" ).elements[0].kind === ts.SyntaxKind.RestType
|
|
112
149
|
TypeQuery,
|
|
113
|
-
// parseType('typeof Number')
|
|
150
|
+
// parseType('typeof Number' ).kind === ts.SyntaxKind.TypeQuery
|
|
114
151
|
TypeOperator,
|
|
115
|
-
// parseType('keyof typeof obj')
|
|
152
|
+
// parseType('keyof typeof obj' ).kind === ts.SyntaxKind.TypeOperator
|
|
116
153
|
KeyOfKeyword,
|
|
117
|
-
//
|
|
154
|
+
// parseType('keyof typeof obj' ).operator === ts.SyntaxKind.KeyOfKeyword
|
|
118
155
|
ConstructorType,
|
|
119
|
-
// parseType('new (...args: any[]) => any')
|
|
156
|
+
// parseType('new (...args: any[]) => any' ).kind === ts.SyntaxKind.ConstructorType
|
|
120
157
|
NamedTupleMember,
|
|
158
|
+
// parseType('[a: 1]' ).elements[0].kind === ts.SyntaxKind.NamedTupleMember
|
|
121
159
|
MappedType,
|
|
122
|
-
// parseType('{[K in TaskType]:
|
|
123
|
-
TypeParameter //
|
|
160
|
+
// parseType('{[K in TaskType]: 123}' ).kind === ts.SyntaxKind.MappedType
|
|
161
|
+
TypeParameter // parseType('{[K in TaskType]: 123}' ).typeParameter.kind === ts.SyntaxKind.TypeParameter
|
|
124
162
|
} = ts.SyntaxKind;
|
|
125
163
|
// console.log({typeArguments, typeName, kind_, node});
|
|
126
164
|
switch (node.kind) {
|
|
@@ -129,12 +167,18 @@ function toSourceTS(node) {
|
|
|
129
167
|
type: 'bigint'
|
|
130
168
|
};
|
|
131
169
|
case BigIntLiteral:
|
|
170
|
+
if (!ts.isBigIntLiteral(node)) {
|
|
171
|
+
throw Error("Impossible");
|
|
172
|
+
}
|
|
132
173
|
const literal = node.text.slice(0, -1); // Remove the "n"
|
|
133
174
|
return {
|
|
134
175
|
type: 'bigint',
|
|
135
176
|
literal
|
|
136
177
|
};
|
|
137
178
|
case ConditionalType:
|
|
179
|
+
if (!ts.isConditionalTypeNode(node)) {
|
|
180
|
+
throw Error("Impossible");
|
|
181
|
+
}
|
|
138
182
|
// Keys on node:
|
|
139
183
|
// ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
|
|
140
184
|
// 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
|
|
@@ -151,6 +195,9 @@ function toSourceTS(node) {
|
|
|
151
195
|
};
|
|
152
196
|
case ConstructorType:
|
|
153
197
|
{
|
|
198
|
+
if (!ts.isConstructorTypeNode(node)) {
|
|
199
|
+
throw Error("Impossible");
|
|
200
|
+
}
|
|
154
201
|
const _parameters = node.parameters.map(toSourceTS);
|
|
155
202
|
const _ret = toSourceTS(node.type);
|
|
156
203
|
return {
|
|
@@ -160,12 +207,18 @@ function toSourceTS(node) {
|
|
|
160
207
|
};
|
|
161
208
|
}
|
|
162
209
|
case FunctionType:
|
|
210
|
+
if (!ts.isFunctionTypeNode(node)) {
|
|
211
|
+
throw Error("Impossible");
|
|
212
|
+
}
|
|
163
213
|
const parameters = node.parameters.map(toSourceTS);
|
|
164
214
|
return {
|
|
165
215
|
type: 'function',
|
|
166
216
|
parameters
|
|
167
217
|
};
|
|
168
218
|
case IndexedAccessType:
|
|
219
|
+
if (!ts.isIndexedAccessTypeNode(node)) {
|
|
220
|
+
throw Error("Impossible");
|
|
221
|
+
}
|
|
169
222
|
const index = toSourceTS(node.indexType);
|
|
170
223
|
const object = toSourceTS(node.objectType);
|
|
171
224
|
return {
|
|
@@ -174,12 +227,18 @@ function toSourceTS(node) {
|
|
|
174
227
|
object
|
|
175
228
|
};
|
|
176
229
|
case RestType:
|
|
230
|
+
if (!ts.isRestTypeNode(node)) {
|
|
231
|
+
throw Error("Impossible");
|
|
232
|
+
}
|
|
177
233
|
const annotation = toSourceTS(node.type);
|
|
178
234
|
return {
|
|
179
235
|
type: 'rest',
|
|
180
236
|
annotation
|
|
181
237
|
};
|
|
182
238
|
case JSDocNullableType:
|
|
239
|
+
if (!ts.isJSDocNullableType(node)) {
|
|
240
|
+
throw Error("Impossible");
|
|
241
|
+
}
|
|
183
242
|
const t = toSourceTS(node.type);
|
|
184
243
|
return {
|
|
185
244
|
type: 'union',
|
|
@@ -187,6 +246,9 @@ function toSourceTS(node) {
|
|
|
187
246
|
};
|
|
188
247
|
case MappedType:
|
|
189
248
|
{
|
|
249
|
+
if (!ts.isMappedTypeNode(node)) {
|
|
250
|
+
throw Error("Impossible");
|
|
251
|
+
}
|
|
190
252
|
const result = toSourceTS(node.type);
|
|
191
253
|
const parameter = node.typeParameter;
|
|
192
254
|
if (parameter.kind === TypeParameter) {
|
|
@@ -206,6 +268,9 @@ function toSourceTS(node) {
|
|
|
206
268
|
// todo work out more: const jsdoc = `(...a: ...number) => 123
|
|
207
269
|
// TS even thinks it's two parameters... just go for array/[]
|
|
208
270
|
case Parameter:
|
|
271
|
+
if (!ts.isParameter(node)) {
|
|
272
|
+
throw Error("Impossible");
|
|
273
|
+
}
|
|
209
274
|
const type = node.type ? toSourceTS(node.type) : 'any';
|
|
210
275
|
const name = toSourceTS(node.name);
|
|
211
276
|
const ret = {
|
|
@@ -220,6 +285,9 @@ function toSourceTS(node) {
|
|
|
220
285
|
}
|
|
221
286
|
return ret;
|
|
222
287
|
case TypeQuery:
|
|
288
|
+
if (!ts.isTypeQueryNode(node)) {
|
|
289
|
+
throw Error("Impossible");
|
|
290
|
+
}
|
|
223
291
|
const argument = toSourceTS(node.exprName);
|
|
224
292
|
// Notify Asserter class that we have to register variables with this name
|
|
225
293
|
if (!requiredTypeofs[argument]) {
|
|
@@ -230,6 +298,9 @@ function toSourceTS(node) {
|
|
|
230
298
|
argument
|
|
231
299
|
};
|
|
232
300
|
case TypeOperator:
|
|
301
|
+
if (!ts.isTypeOperatorNode(node)) {
|
|
302
|
+
throw Error("Impossible");
|
|
303
|
+
}
|
|
233
304
|
if (node.operator === KeyOfKeyword) {
|
|
234
305
|
const _argument = toSourceTS(node.type);
|
|
235
306
|
return {
|
|
@@ -240,6 +311,9 @@ function toSourceTS(node) {
|
|
|
240
311
|
console.warn("unimplemented TypeOperator", node);
|
|
241
312
|
case TypeReference:
|
|
242
313
|
{
|
|
314
|
+
if (!ts.isTypeReferenceNode(node)) {
|
|
315
|
+
throw Error("Impossible");
|
|
316
|
+
}
|
|
243
317
|
if ((typeName.text === 'Object' || typeName.text === 'Record') && (typeArguments == null ? void 0 : typeArguments.length) === 2) {
|
|
244
318
|
return {
|
|
245
319
|
type: 'record',
|
|
@@ -295,14 +369,16 @@ function toSourceTS(node) {
|
|
|
295
369
|
args
|
|
296
370
|
};
|
|
297
371
|
}
|
|
298
|
-
case StringKeyword:
|
|
299
|
-
return node.getText();
|
|
300
|
-
case NumberKeyword:
|
|
301
|
-
return node.getText();
|
|
302
372
|
case NamedTupleMember:
|
|
373
|
+
if (!ts.isNamedTupleMember(node)) {
|
|
374
|
+
throw Error("Impossible");
|
|
375
|
+
}
|
|
303
376
|
return toSourceTS(node.type);
|
|
304
377
|
case IntersectionType:
|
|
305
378
|
{
|
|
379
|
+
if (!ts.isIntersectionTypeNode(node)) {
|
|
380
|
+
throw Error("Impossible");
|
|
381
|
+
}
|
|
306
382
|
const _members = node.types.map(toSourceTS);
|
|
307
383
|
return {
|
|
308
384
|
type: 'intersection',
|
|
@@ -310,35 +386,87 @@ function toSourceTS(node) {
|
|
|
310
386
|
};
|
|
311
387
|
}
|
|
312
388
|
case TupleType:
|
|
389
|
+
if (!ts.isTupleTypeNode(node)) {
|
|
390
|
+
throw Error("Impossible");
|
|
391
|
+
}
|
|
313
392
|
const elements = node.elements.map(toSourceTS);
|
|
314
393
|
return {
|
|
315
394
|
type: 'tuple',
|
|
316
395
|
elements
|
|
317
396
|
};
|
|
318
397
|
case UnionType:
|
|
398
|
+
if (!ts.isUnionTypeNode(node)) {
|
|
399
|
+
throw Error("Impossible");
|
|
400
|
+
}
|
|
319
401
|
const members = node.types.map(toSourceTS);
|
|
320
402
|
return {
|
|
321
403
|
type: 'union',
|
|
322
404
|
members
|
|
323
405
|
};
|
|
324
406
|
case TypeLiteral:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
properties
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
407
|
+
{
|
|
408
|
+
if (!ts.isTypeLiteralNode(node)) {
|
|
409
|
+
throw Error("Impossible");
|
|
410
|
+
}
|
|
411
|
+
const properties = {};
|
|
412
|
+
/** @type {object[]} */
|
|
413
|
+
let indexSignatures;
|
|
414
|
+
node.members.forEach(member => {
|
|
415
|
+
if (member.kind === IndexSignature) {
|
|
416
|
+
var _indexSignatures;
|
|
417
|
+
indexSignatures = (_indexSignatures = indexSignatures) != null ? _indexSignatures : [];
|
|
418
|
+
indexSignatures.push(toSourceTS(member));
|
|
419
|
+
} else if (member.kind === PropertySignature) {
|
|
420
|
+
if (!ts.isPropertySignature(member)) {
|
|
421
|
+
throw Error("Impossible");
|
|
422
|
+
}
|
|
423
|
+
const name = toSourceTS(member.name);
|
|
424
|
+
const type = toSourceTS(member.type);
|
|
425
|
+
properties[name] = type;
|
|
426
|
+
} else {
|
|
427
|
+
console.warn('TypeLiteral: unhandled member', member);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
const _ret2 = {
|
|
431
|
+
type: 'object'
|
|
432
|
+
};
|
|
433
|
+
if (Object.keys(properties).length) {
|
|
434
|
+
_ret2.properties = properties;
|
|
435
|
+
}
|
|
436
|
+
if (indexSignatures) {
|
|
437
|
+
_ret2.indexSignatures = indexSignatures;
|
|
438
|
+
}
|
|
439
|
+
return _ret2;
|
|
440
|
+
}
|
|
335
441
|
case PropertySignature:
|
|
442
|
+
if (!ts.isPropertySignature(node)) {
|
|
443
|
+
throw Error("Impossible");
|
|
444
|
+
}
|
|
336
445
|
console.warn('toSourceTS> should not happen, handled by TypeLiteral directly');
|
|
337
446
|
return `${toSourceTS(node.name)}: ${toSourceTS(node.type)}`;
|
|
447
|
+
case IndexSignature:
|
|
448
|
+
if (!ts.isIndexSignatureDeclaration(node)) {
|
|
449
|
+
throw Error("Impossible");
|
|
450
|
+
}
|
|
451
|
+
// Only possible modifier I know of, but we don't need it:
|
|
452
|
+
// {readonly [n: number]: string, length: number}
|
|
453
|
+
const indexType = toSourceTS(node.type);
|
|
454
|
+
const indexParameters = node.parameters.map(toSourceTS);
|
|
455
|
+
return {
|
|
456
|
+
type: 'indexSignature',
|
|
457
|
+
indexType,
|
|
458
|
+
indexParameters
|
|
459
|
+
};
|
|
338
460
|
case Identifier:
|
|
461
|
+
if (!ts.isIdentifier(node)) {
|
|
462
|
+
throw Error("Impossible");
|
|
463
|
+
}
|
|
339
464
|
return node.text;
|
|
340
465
|
case ArrayType:
|
|
341
466
|
{
|
|
467
|
+
if (!ts.isArrayTypeNode(node)) {
|
|
468
|
+
throw Error("Impossible");
|
|
469
|
+
}
|
|
342
470
|
const elementType = toSourceTS(node.elementType);
|
|
343
471
|
return {
|
|
344
472
|
type: 'array',
|
|
@@ -346,18 +474,23 @@ function toSourceTS(node) {
|
|
|
346
474
|
};
|
|
347
475
|
}
|
|
348
476
|
case LiteralType:
|
|
477
|
+
if (!ts.isLiteralTypeNode(node)) {
|
|
478
|
+
throw Error("Impossible");
|
|
479
|
+
}
|
|
349
480
|
return toSourceTS(node.literal);
|
|
350
481
|
case AnyKeyword:
|
|
351
482
|
case BooleanKeyword:
|
|
352
|
-
|
|
353
|
-
case
|
|
483
|
+
case StringKeyword:
|
|
484
|
+
case NeverKeyword:
|
|
354
485
|
case NullKeyword:
|
|
355
|
-
case
|
|
356
|
-
case ThisType:
|
|
486
|
+
case NumberKeyword:
|
|
357
487
|
case UndefinedKeyword:
|
|
358
|
-
case VoidKeyword:
|
|
359
488
|
case UnknownKeyword:
|
|
360
|
-
case
|
|
489
|
+
case VoidKeyword:
|
|
490
|
+
// ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
|
|
491
|
+
case JSDocAllType:
|
|
492
|
+
case ThisType:
|
|
493
|
+
case StringLiteral:
|
|
361
494
|
return node.getText();
|
|
362
495
|
case TrueKeyword:
|
|
363
496
|
return true;
|
|
@@ -371,9 +504,16 @@ function toSourceTS(node) {
|
|
|
371
504
|
properties: {}
|
|
372
505
|
};
|
|
373
506
|
case ParenthesizedType:
|
|
507
|
+
if (!ts.isParenthesizedTypeNode(node)) {
|
|
508
|
+
throw Error("Impossible");
|
|
509
|
+
}
|
|
374
510
|
// fall-through for parentheses
|
|
375
511
|
return toSourceTS(node.type);
|
|
376
|
-
case
|
|
512
|
+
case ImportType:
|
|
513
|
+
if (!ts.isImportTypeNode(node)) {
|
|
514
|
+
throw Error("Impossible");
|
|
515
|
+
}
|
|
516
|
+
/** @todo Handle case without any qualifier like `import('test')` */
|
|
377
517
|
return toSourceTS(node.qualifier);
|
|
378
518
|
default:
|
|
379
519
|
// const test = {};
|
|
@@ -588,18 +728,25 @@ function simplifyType(type, optional) {
|
|
|
588
728
|
return type;
|
|
589
729
|
}
|
|
590
730
|
|
|
731
|
+
/**
|
|
732
|
+
* @typedef {ReturnType<typeof parseJSDoc>} ParseJSDocReturnType
|
|
733
|
+
*/
|
|
734
|
+
/**
|
|
735
|
+
* @typedef {typeof expandTypeDepFree} ExpandType
|
|
736
|
+
* @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
|
|
737
|
+
*/
|
|
591
738
|
/**
|
|
592
739
|
* Parses JSDoc comments to extract parameter type information.
|
|
593
740
|
*
|
|
594
741
|
* @param {string} src - The JSDoc comment string to parse.
|
|
595
|
-
* @param {
|
|
596
|
-
* @returns {Record<string,
|
|
742
|
+
* @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
|
|
743
|
+
* @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
|
|
597
744
|
*/
|
|
598
745
|
function parseJSDoc(src, expandType = expandTypeDepFree) {
|
|
599
746
|
// Parse something like: @param {Object} [kwargs={}] Optional arguments.
|
|
600
747
|
const regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\{\}\.'" ]+)/g;
|
|
601
748
|
const matches = [...src.matchAll(regex)];
|
|
602
|
-
/** @type {Record<string,
|
|
749
|
+
/** @type {Record<string, ExpandTypeReturnType>} */
|
|
603
750
|
const params = Object.create(null);
|
|
604
751
|
matches.forEach(_ => {
|
|
605
752
|
const type = expandType(_[1].trim());
|
|
@@ -619,44 +766,31 @@ function parseJSDoc(src, expandType = expandTypeDepFree) {
|
|
|
619
766
|
// Strip the rest (either leftover of optional value or description)
|
|
620
767
|
name = name.split(' ')[0].split('=')[0].trim();
|
|
621
768
|
const simplifiedType = simplifyType(type, optional);
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
// Something like: @param {number} description[].components
|
|
633
|
-
let parts0 = parts[0]; // description[]
|
|
634
|
-
const parts1 = parts[1]; // components
|
|
635
|
-
if (parts0.endsWith('[]')) {
|
|
636
|
-
parts0 = parts0.slice(0, -2); // description[] -> description
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const toptype = params[parts0];
|
|
640
|
-
if ((toptype == null ? void 0 : toptype.type) === "union") {
|
|
769
|
+
// Turn "options.stats[].unitsName" into ['options', 'stats', 'unitsName'].
|
|
770
|
+
const parts = name.split(/[\[\]]*\./);
|
|
771
|
+
let properties = params;
|
|
772
|
+
for (const part of parts) {
|
|
773
|
+
const toptype = properties[part];
|
|
774
|
+
if (!toptype) {
|
|
775
|
+
// No toptype means we resolved as far as possible, now we can add `simplifiedType`.
|
|
776
|
+
console.assert(part === parts.at(-1), 'Current part and last part should be the same.');
|
|
777
|
+
properties[part] = simplifiedType;
|
|
778
|
+
} else if (toptype.type === "union") {
|
|
641
779
|
const typeObject = toptype.members.find(_ => (_ == null ? void 0 : _.type) === 'object');
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
toptype.properties[parts1] = simplifiedType;
|
|
780
|
+
properties = typeObject.properties;
|
|
781
|
+
} else if (toptype.type === "array") {
|
|
782
|
+
properties = toptype.elementType.properties;
|
|
783
|
+
} else if (toptype.type === "object") {
|
|
784
|
+
toptype.properties = toptype.properties || Object.create(null);
|
|
785
|
+
properties = toptype.properties;
|
|
649
786
|
} else {
|
|
650
|
-
console.warn("parseJSDoc>
|
|
787
|
+
console.warn("parseJSDoc> Skipping @param, unseen syntax detected. Please check if your JSDoc is valid or open an issue about this!", {
|
|
651
788
|
src,
|
|
652
789
|
toptype,
|
|
653
|
-
|
|
654
|
-
parts1,
|
|
790
|
+
parts,
|
|
655
791
|
simplifiedType
|
|
656
792
|
});
|
|
657
793
|
}
|
|
658
|
-
} else {
|
|
659
|
-
params[name] = simplifiedType;
|
|
660
794
|
}
|
|
661
795
|
});
|
|
662
796
|
if (Object.keys(params).length === 0) {
|
|
@@ -682,6 +816,34 @@ function parseJSDocSetter(src, expandType = expandTypeDepFree) {
|
|
|
682
816
|
}
|
|
683
817
|
}
|
|
684
818
|
|
|
819
|
+
/**
|
|
820
|
+
* @typedef {typeof expandTypeDepFree} ExpandType
|
|
821
|
+
* @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
|
|
822
|
+
*/
|
|
823
|
+
/**
|
|
824
|
+
* Parses JSDoc comments to extract parameter type information.
|
|
825
|
+
*
|
|
826
|
+
* @param {string} src - The JSDoc comment string to parse.
|
|
827
|
+
* @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
|
|
828
|
+
* @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping template names to their parsed types,
|
|
829
|
+
* or `undefined` if no template tags were found.
|
|
830
|
+
*/
|
|
831
|
+
function parseJSDocTemplates(src, expandType = expandTypeDepFree) {
|
|
832
|
+
const regexTemplateTyped = /@template \{(.*?)\} ([a-zA-Z0-9_$=]+)/g;
|
|
833
|
+
const matches = [...src.matchAll(regexTemplateTyped)];
|
|
834
|
+
if (!matches.length) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
/** @type {Record<string, ExpandTypeReturnType>} */
|
|
838
|
+
const templates = Object.create(null);
|
|
839
|
+
matches.forEach(_ => {
|
|
840
|
+
const type = expandType(_[1].trim());
|
|
841
|
+
const name = _[2].trim();
|
|
842
|
+
templates[name] = type;
|
|
843
|
+
});
|
|
844
|
+
return templates;
|
|
845
|
+
}
|
|
846
|
+
|
|
685
847
|
/**
|
|
686
848
|
* Extracts the parameter name and its optionality from a JSDoc parameter string.
|
|
687
849
|
*
|
|
@@ -2631,14 +2793,14 @@ class Stringifier {
|
|
|
2631
2793
|
}
|
|
2632
2794
|
|
|
2633
2795
|
/** @typedef {import('@babel/types').Node } Node */
|
|
2634
|
-
/** @typedef {import(
|
|
2635
|
-
/** @typedef {import(
|
|
2636
|
-
/** @typedef {import('./stat.js').Stat
|
|
2796
|
+
/** @typedef {import('@babel/types').ClassMethod } ClassMethod */
|
|
2797
|
+
/** @typedef {import('@babel/types').ClassPrivateMethod} ClassPrivateMethod */
|
|
2798
|
+
/** @typedef {import('./stat.js').Stat } Stat */
|
|
2637
2799
|
/**
|
|
2638
2800
|
* @typedef {object} Options
|
|
2639
2801
|
* @property {boolean} [forceCurly] - Determines whether curly braces are enforced in Stringifier.
|
|
2640
2802
|
* @property {boolean} [validateDivision] - Indicates whether division operations should be validated.
|
|
2641
|
-
* @property {
|
|
2803
|
+
* @property {import('./parseJSDoc.js').ExpandType} [expandType] - A function that expands shorthand types into full descriptions.
|
|
2642
2804
|
* @property {string} [filename] - The name of a file to which the instance pertains.
|
|
2643
2805
|
* @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
|
|
2644
2806
|
* @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
|
|
@@ -2891,9 +3053,9 @@ class Asserter extends Stringifier {
|
|
|
2891
3053
|
}
|
|
2892
3054
|
/**
|
|
2893
3055
|
* @param {Node} node - The Babel AST node.
|
|
2894
|
-
* @returns {undefined
|
|
3056
|
+
* @returns {string|undefined} The JSDoc comment of `node`.
|
|
2895
3057
|
*/
|
|
2896
|
-
|
|
3058
|
+
getLeadingComment(node) {
|
|
2897
3059
|
if (node.type === 'BlockStatement') {
|
|
2898
3060
|
node = this.parent;
|
|
2899
3061
|
}
|
|
@@ -2919,28 +3081,56 @@ class Asserter extends Stringifier {
|
|
|
2919
3081
|
if (leadingComments && leadingComments.length) {
|
|
2920
3082
|
const lastComment = leadingComments[leadingComments.length - 1];
|
|
2921
3083
|
if (lastComment.type === "CommentBlock") {
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
3084
|
+
return lastComment.value;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
/**
|
|
3089
|
+
* @param {Node} node - The Babel AST node.
|
|
3090
|
+
* @todo ESLint problem:
|
|
3091
|
+
* returns {import('./parseJSDoc.js').ParseJSDocReturnType} The return value of `parseJSDoc`
|
|
3092
|
+
* returns {Record<string, import('./parseJSDoc.js').ExpandTypeReturnType> | undefined} The
|
|
3093
|
+
* return value of `parseJSDoc`.
|
|
3094
|
+
* @returns {Record<string, any>|undefined} asd
|
|
3095
|
+
*/
|
|
3096
|
+
getJSDoc(node) {
|
|
3097
|
+
const comment = this.getLeadingComment(node);
|
|
3098
|
+
if (!comment) {
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
if (comment.includes('@event')) {
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
if (comment.includes('@ignoreRTI')) {
|
|
3105
|
+
return;
|
|
3106
|
+
}
|
|
3107
|
+
// Need to do same resolving as in: this.getLeadingComment(node)
|
|
3108
|
+
if (node.type === 'BlockStatement') {
|
|
3109
|
+
node = this.parent;
|
|
3110
|
+
}
|
|
3111
|
+
if (node.type === 'ClassMethod' && node.kind === 'set') {
|
|
3112
|
+
const paramName = this.getNameOfParam(node.params[0]);
|
|
3113
|
+
if (node.params.length !== 1) {
|
|
3114
|
+
this.warn("getJSDoc> setters require exactly one argument");
|
|
2942
3115
|
}
|
|
3116
|
+
const setterType = parseJSDocSetter(comment, this.expandType);
|
|
3117
|
+
if (!setterType) {
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
const _params = {
|
|
3121
|
+
[paramName]: setterType
|
|
3122
|
+
};
|
|
3123
|
+
return {
|
|
3124
|
+
templates: undefined,
|
|
3125
|
+
params: _params
|
|
3126
|
+
};
|
|
2943
3127
|
}
|
|
3128
|
+
const templates = parseJSDocTemplates(comment);
|
|
3129
|
+
const params = parseJSDoc(comment, this.expandType);
|
|
3130
|
+
return {
|
|
3131
|
+
templates,
|
|
3132
|
+
params
|
|
3133
|
+
};
|
|
2944
3134
|
}
|
|
2945
3135
|
/**
|
|
2946
3136
|
* Retrieves the name of a parameter from a Babel AST node.
|
|
@@ -2959,8 +3149,7 @@ class Asserter extends Stringifier {
|
|
|
2959
3149
|
return param.left.name;
|
|
2960
3150
|
}
|
|
2961
3151
|
}
|
|
2962
|
-
|
|
2963
|
-
this.warn("unable to extra name from param in specified way - may contain too much information");
|
|
3152
|
+
this.warn("Unable to retrieve name from param in specified way - may contain too much information.");
|
|
2964
3153
|
return this.toSource(param);
|
|
2965
3154
|
}
|
|
2966
3155
|
statsReset() {
|
|
@@ -3079,6 +3268,17 @@ class Asserter extends Stringifier {
|
|
|
3079
3268
|
stat.unchecked++;
|
|
3080
3269
|
return '';
|
|
3081
3270
|
}
|
|
3271
|
+
const {
|
|
3272
|
+
templates,
|
|
3273
|
+
params
|
|
3274
|
+
} = jsdoc;
|
|
3275
|
+
if (!params) {
|
|
3276
|
+
console.warn("This should never happen, please check your input code.", this.getLeadingComment(node), {
|
|
3277
|
+
jsdoc
|
|
3278
|
+
});
|
|
3279
|
+
stat.unchecked++;
|
|
3280
|
+
return '';
|
|
3281
|
+
}
|
|
3082
3282
|
stat.checked++;
|
|
3083
3283
|
const {
|
|
3084
3284
|
spaces
|
|
@@ -3089,17 +3289,21 @@ class Asserter extends Stringifier {
|
|
|
3089
3289
|
if (this.ignoreLocations.includes(loc)) {
|
|
3090
3290
|
return '// IGNORE RTI TYPE VALIDATIONS, KNOWN ISSUES\n';
|
|
3091
3291
|
}
|
|
3292
|
+
if (templates) {
|
|
3293
|
+
const tmp = JSON.stringify(templates, null, 2).replaceAll('\n', '\n' + spaces);
|
|
3294
|
+
out += `\n${spaces}const rtiTemplates = ${tmp};`;
|
|
3295
|
+
}
|
|
3092
3296
|
//out += `${spaces}/*${spaces} node.type=${node.type}\n${spaces}
|
|
3093
3297
|
// ${JSON.stringify(jsdoc)}\n${parent}\n${spaces}*/\n`;
|
|
3094
|
-
for (let name in
|
|
3095
|
-
const type =
|
|
3298
|
+
for (let name in params) {
|
|
3299
|
+
const type = params[name];
|
|
3096
3300
|
const hasParam = this.nodeHasParamName(node, name);
|
|
3097
3301
|
if (!hasParam) {
|
|
3098
3302
|
let testNode = node;
|
|
3099
3303
|
if (node.type === 'BlockStatement') {
|
|
3100
3304
|
testNode = this.parent;
|
|
3101
3305
|
}
|
|
3102
|
-
const paramIndex = Object.keys(
|
|
3306
|
+
const paramIndex = Object.keys(params).findIndex(_ => _ === name);
|
|
3103
3307
|
const param = testNode.params[paramIndex];
|
|
3104
3308
|
if (param) {
|
|
3105
3309
|
const isObjectPattern = param.type === 'ObjectPattern';
|
|
@@ -3129,7 +3333,11 @@ class Asserter extends Stringifier {
|
|
|
3129
3333
|
continue;
|
|
3130
3334
|
}
|
|
3131
3335
|
const _t = JSON.stringify(type.elementType, null, 2).replaceAll('\n', '\n' + spaces);
|
|
3132
|
-
|
|
3336
|
+
if (templates) {
|
|
3337
|
+
out += `${spaces}if (!inspectTypeWithTemplates(${element.name}, ${_t}, '${_loc}', '${name}', rtiTemplates)) {\n`;
|
|
3338
|
+
} else {
|
|
3339
|
+
out += `${spaces}if (!inspectType(${element.name}, ${_t}, '${_loc}', '${name}')) {\n`;
|
|
3340
|
+
}
|
|
3133
3341
|
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
3134
3342
|
}
|
|
3135
3343
|
continue;
|
|
@@ -3151,7 +3359,11 @@ class Asserter extends Stringifier {
|
|
|
3151
3359
|
continue;
|
|
3152
3360
|
}
|
|
3153
3361
|
const _t2 = JSON.stringify(subType, null, 2).replaceAll('\n', '\n' + spaces);
|
|
3154
|
-
|
|
3362
|
+
if (templates) {
|
|
3363
|
+
out += `${spaces}if (!inspectTypeWithTemplates(${keyName}, ${_t2}, '${_loc}', '${name}', rtiTemplates)) {\n`;
|
|
3364
|
+
} else {
|
|
3365
|
+
out += `${spaces}if (!inspectType(${keyName}, ${_t2}, '${_loc}', '${name}')) {\n`;
|
|
3366
|
+
}
|
|
3155
3367
|
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
3156
3368
|
}
|
|
3157
3369
|
continue;
|
|
@@ -3184,7 +3396,11 @@ class Asserter extends Stringifier {
|
|
|
3184
3396
|
out += '\n';
|
|
3185
3397
|
first = false;
|
|
3186
3398
|
}
|
|
3187
|
-
|
|
3399
|
+
if (templates) {
|
|
3400
|
+
out += `${spaces}if (${prevCheck}!inspectTypeWithTemplates(${name}, ${t}, '${_loc3}', '${name}', rtiTemplates)) {\n`;
|
|
3401
|
+
} else {
|
|
3402
|
+
out += `${spaces}if (${prevCheck}!inspectType(${name}, ${t}, '${_loc3}', '${name}')) {\n`;
|
|
3403
|
+
}
|
|
3188
3404
|
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
3189
3405
|
}
|
|
3190
3406
|
return out;
|
|
@@ -3710,4 +3926,4 @@ function toSourceBabelTS(node) {
|
|
|
3710
3926
|
}
|
|
3711
3927
|
}
|
|
3712
3928
|
|
|
3713
|
-
export { Asserter, Stringifier, addTypeChecks, ast2json, ast2jsonForComparison, code2ast2code, compareAST, expandType, expandTypeBabelTS, expandTypeDepFree, extractCurlyContent, extractNameAndOptionality, nodeIsFunction, parseJSDoc, parseJSDocSetter, parseJSDocTypedef, parseType, parseTypeBabelTS, requiredTypeofs, simplifyType, toSourceBabelTS, toSourceTS, trimEndSpaces };
|
|
3929
|
+
export { Asserter, Stringifier, addTypeChecks, ast2json, ast2jsonForComparison, code2ast2code, compareAST, expandType, expandTypeBabelTS, expandTypeDepFree, extractCurlyContent, extractNameAndOptionality, nodeIsFunction, parseJSDoc, parseJSDocSetter, parseJSDocTemplates, parseJSDocTypedef, parseType, parseTypeBabelTS, requiredTypeofs, simplifyType, toSourceBabelTS, toSourceTS, trimEndSpaces };
|