@runtime-type-inspector/transpiler 2.2.2 → 2.2.4

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.
Files changed (4) hide show
  1. package/index.cjs +1068 -35
  2. package/index.mjs +1063 -33
  3. package/package.json +3 -3
  4. package/types.d.ts +0 -970
package/index.mjs CHANGED
@@ -1,11 +1,47 @@
1
1
  import { parse } from '@babel/parser';
2
2
  import ts from 'typescript';
3
3
 
4
+ /**
5
+ * @todo expandTypeDepFree doesn't support "complicated" types.
6
+ * For actual builds, we use expandType() anyway (which is based on TypeScript).
7
+ * But since TypeScript is a huge dependency, I'm looking into BabelFlow/BabelTypescript parser.
8
+ * Comparing AST's like this usually helps to find bugs or potential issues,
9
+ * while we can benchmark for best performance too.
10
+ * @example
11
+ * const tooComplex = 'Array<string|{chunks?: undefined|Array<{language: string|null, timestamp: Array<number|null>, text: string}>}>';
12
+ * console.log(expandTypeDepFree(tooComplex));
13
+ */
14
+ /**
15
+ * @typedef {object} ExpandTypeReturnValue
16
+ * @property {'array' | 'union' | 'record' | 'tuple' | 'object' | 'promise' | 'typeof'} type - The type.
17
+ * @property {object | string} [elementType] - For Array.
18
+ * @property {object | string} [key] - For Record<key, val>
19
+ * @property {object | string} [val] - For Record<key, val>
20
+ * @property {(object | string)[]} [members] - For unions.
21
+ * @property {object | string} [properties] - For objects.
22
+ * @property {(object | string)[]} [elements] - For tuples.
23
+ * @property {object | string} [argument] - For typeof.
24
+ */
25
+ /**
26
+ * 'DepFree' refers to the fact that this function has no dependencies,
27
+ * while `expandType` depends on TypeScript itself for maximum compatibility.
28
+ * @example
29
+ * expandTypeDepFree('(123) '); // Outputs: '123'
30
+ * expandTypeDepFree('Array<number> '); // Outputs: { type: 'array', elementType: 'number' }
31
+ * expandTypeDepFree('Array<(123) > '); // Outputs: { type: 'array', elementType: '123' }
32
+ * expandTypeDepFree(' ( ( 123 ) ) '); // Outputs: '123'
33
+ * expandTypeDepFree(' (string ) |(number ) '); // Outputs: { type: 'union', members: ['string', 'number'] }
34
+ * expandTypeDepFree(' (( Object ) ) '); // Outputs: { type: 'object', properties: {} }
35
+ * @param {string} type - The input type to expand.
36
+ * @returns {string | ExpandTypeReturnValue} Object containing parsed information from type string.
37
+ */
4
38
  function expandTypeDepFree(type) {
5
39
  type = type.trim();
40
+ // '(123)' -> '123'
6
41
  while (!type.includes('|') && type[0] === '(' && type[type.length - 1] === ')') {
7
42
  type = type.slice(1, -1).trim();
8
43
  }
44
+ // (1) Rest parameters like ...string
9
45
  if (type[0] === '.' && type[1] === '.' && type[2] === '.') {
10
46
  const elementType = type.slice(3);
11
47
  return {
@@ -13,6 +49,8 @@ function expandTypeDepFree(type) {
13
49
  elementType
14
50
  };
15
51
  }
52
+ // (2)
53
+ // Array<...>
16
54
  if (type.startsWith("Array<") && type.endsWith('>')) {
17
55
  const typeSlice = type.slice(6, -1);
18
56
  const elementType = expandTypeDepFree(typeSlice);
@@ -21,6 +59,7 @@ function expandTypeDepFree(type) {
21
59
  elementType
22
60
  };
23
61
  }
62
+ // Promise<...>
24
63
  if (type.startsWith("Promise<") && type.endsWith('>')) {
25
64
  const typeSlice = type.slice(8, -1);
26
65
  const elementType = expandTypeDepFree(typeSlice);
@@ -29,6 +68,7 @@ function expandTypeDepFree(type) {
29
68
  elementType
30
69
  };
31
70
  }
71
+ // (3) Object<...> or Record<...>
32
72
  if ((type.startsWith("Object<") || type.startsWith("Record<")) && type.endsWith('>')) {
33
73
  const recordSlice = type.slice(7, -1);
34
74
  const firstComma = recordSlice.indexOf(',');
@@ -43,8 +83,9 @@ function expandTypeDepFree(type) {
43
83
  val: expandTypeDepFree(val)
44
84
  };
45
85
  }
86
+ // (4) {...}
46
87
  if (type[0] === '{' && type[type.length - 1] === '}') {
47
- const propertiesArray = type.slice(1, -1).split(',');
88
+ const propertiesArray = type.slice(1, -1).split(','); // ['entity: Entity', ' app: AppBase']
48
89
  const properties = {};
49
90
  propertiesArray.forEach(_ => {
50
91
  const [propName, propType] = _.split(":").map(_ => _.trim());
@@ -59,6 +100,7 @@ function expandTypeDepFree(type) {
59
100
  properties
60
101
  };
61
102
  }
103
+ // (5) expand unions
62
104
  const members = type.split("|");
63
105
  if (members.length >= 2) {
64
106
  members.forEach((_, i) => members[i] = _.trim());
@@ -67,6 +109,8 @@ function expandTypeDepFree(type) {
67
109
  members: members.map(expandTypeDepFree)
68
110
  };
69
111
  }
112
+ // (6) expand [] Arrays
113
+ // Test arrays: new pc.Mat3().set([1, 2, 3, "asd"])
70
114
  if (type.endsWith("[]")) {
71
115
  const typeSlice = type.slice(0, -2);
72
116
  return {
@@ -74,13 +118,15 @@ function expandTypeDepFree(type) {
74
118
  elementType: expandTypeDepFree(typeSlice)
75
119
  };
76
120
  }
121
+ // (7) expand tuples
77
122
  if (type[0] === '[' && type[type.length - 1] === ']') {
78
- const elements = type.slice(1, -1).split(',');
123
+ const elements = type.slice(1, -1).split(','); // ['null', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture']
79
124
  return {
80
125
  type: 'tuple',
81
126
  elements: elements.map(expandTypeDepFree)
82
127
  };
83
128
  }
129
+ // (8) expand typeof expressions
84
130
  if (type.startsWith('typeof ')) {
85
131
  const argument = expandTypeDepFree(type.substring(7));
86
132
  return {
@@ -97,6 +143,14 @@ function expandTypeDepFree(type) {
97
143
  return type;
98
144
  }
99
145
 
146
+ /** @typedef {import('@babel/types').Node} Node */
147
+ /** @typedef {import('@babel/types').Function} Function */
148
+ /**
149
+ * Checks if the provided node is a function-like structure.
150
+ *
151
+ * @param {Node} node - The Babel AST node to be tested.
152
+ * @returns {node is Function} - `true` if the node is a function-like structure, otherwise `false`.
153
+ */
100
154
  function nodeIsFunction(node) {
101
155
  switch (node.type) {
102
156
  case 'ArrowFunctionExpression':
@@ -110,12 +164,23 @@ function nodeIsFunction(node) {
110
164
  return false;
111
165
  }
112
166
 
167
+ /**
168
+ * @typedef DocType
169
+ * @property {boolean} optional - Type is optional.
170
+ */
171
+ /**
172
+ * @param {string | DocType} type - The type.
173
+ * @param {boolean} optional - Optionality
174
+ * @returns {string | DocType} The simplified type.
175
+ */
113
176
  function simplifyType(type, optional) {
177
+ // If it's already an object, just set optionality.
114
178
  if (type instanceof Object) {
115
179
  type.optional = optional;
116
180
  } else if (typeof type === 'string') {
117
181
  type = type.trim();
118
182
  if (type !== 'object' && type !== 'object[]' && type !== 'union' && !optional) {
183
+ // console.log("simplify", type);
119
184
  return type;
120
185
  }
121
186
  type = {
@@ -128,43 +193,65 @@ function simplifyType(type, optional) {
128
193
  }
129
194
  if (type.type === 'object' && type.properties && Object.keys(type.properties).length === 0) {
130
195
  delete type.properties;
196
+ // console.log("delete empty", type);
131
197
  }
198
+
132
199
  return type;
133
200
  }
134
201
 
202
+ /**
203
+ * Parses JSDoc comments to extract parameter type information.
204
+ *
205
+ * @param {string} src - The JSDoc comment string to parse.
206
+ * @param {Function} [expandType] - An optional function to process the types found in the JSDoc.
207
+ * @returns {Record<string, any> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
208
+ */
135
209
  function parseJSDoc(src, expandType = expandTypeDepFree) {
210
+ // Parse something like: @param {Object} [kwargs={}] Optional arguments.
136
211
  const regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\{\}\.'" ]+)/g;
137
212
  const matches = [...src.matchAll(regex)];
213
+ /** @type {Record<string, any>} */
138
214
  const params = Object.create(null);
139
215
  matches.forEach(_ => {
140
216
  const type = expandType(_[1].trim());
141
217
  let name = _[2].trim();
142
218
  let optional = false;
219
+ // Examples:
220
+ // name: [kwargs={}] The configuration parameters.
221
+ // name: [d = 1.0] Sample spacing
143
222
  if (name[0] === '[') {
223
+ // Possible improvement: counting opening/closing brackets for perfect match
144
224
  const closer = name.lastIndexOf(']');
225
+ // Afterwards name will be: d = 1.0
145
226
  name = name.substring(1, closer);
227
+ // mark it for the type:
146
228
  optional = true;
147
229
  }
230
+ // Strip the rest (either leftover of optional value or description)
148
231
  name = name.split(' ')[0].split('=')[0].trim();
149
232
  const simplifiedType = simplifyType(type, optional);
150
233
  const parts = name.split(".");
151
234
  if (parts.length === 3) {
152
- const parts0 = parts[0];
153
- const parts1 = parts[1];
154
- const parts2 = parts[2];
235
+ // Something like: @param {number[]} settings.render.skyboxRotation - Rotation of skybox.
236
+ const parts0 = parts[0]; // settings
237
+ const parts1 = parts[1]; // render
238
+ const parts2 = parts[2]; // skyboxRotation
155
239
  const toptype = params[parts0];
156
240
  toptype.properties[parts1].properties = toptype.properties[parts1].properties || {};
157
241
  toptype.properties[parts1].properties[parts2] = simplifiedType;
158
242
  } else if (parts.length === 2) {
159
- let parts0 = parts[0];
160
- const parts1 = parts[1];
243
+ // Something like: @param {number} description[].components
244
+ let parts0 = parts[0]; // description[]
245
+ const parts1 = parts[1]; // components
161
246
  if (parts0.endsWith('[]')) {
162
- parts0 = parts0.slice(0, -2);
247
+ parts0 = parts0.slice(0, -2); // description[] -> description
163
248
  }
249
+
164
250
  const toptype = params[parts0];
165
251
  if ((toptype == null ? void 0 : toptype.type) === "union") {
166
252
  const typeObject = toptype.members.find(_ => (_ == null ? void 0 : _.type) === 'object');
167
253
  typeObject.properties[parts1] = simplifiedType;
254
+ //typeObject.properties = simplifiedType; // todo add test case
168
255
  } else if ((toptype == null ? void 0 : toptype.type) === "array") {
169
256
  toptype.elementType.properties[parts1] = simplifiedType;
170
257
  } else if ((toptype == null ? void 0 : toptype.type) === "object") {
@@ -189,29 +276,61 @@ function parseJSDoc(src, expandType = expandTypeDepFree) {
189
276
  return params;
190
277
  }
191
278
 
279
+ /**
280
+ * @param {string} src - JSDoc comment of the setter.
281
+ * @param {Function} expandType - The expandType function.
282
+ * @returns {string | DocType | undefined} The parsed and possibly expanded type from
283
+ * the JSDoc comment, or undefined if parsing fails to find `@type`.
284
+ */
192
285
  function parseJSDocSetter(src, expandType = expandTypeDepFree) {
193
286
  const regex = /@type \{(.*?)\}/g;
194
287
  const matches = [...src.matchAll(regex)];
195
288
  if (matches.length === 1) {
196
289
  const match = matches[0];
197
290
  const type = expandType(match[1]);
198
- const simplifiedType = simplifyType(type, false);
291
+ const simplifiedType = simplifyType(type, /* optional */false);
199
292
  return simplifiedType;
200
293
  }
201
294
  }
202
295
 
296
+ /**
297
+ * Extracts the parameter name and its optionality from a JSDoc parameter string.
298
+ *
299
+ * This function takes a rest parameter string from a JSDoc comment, trims it, and determines the parameter's
300
+ * name and whether it is optional. The optionality is inferred based on the presence of square brackets around
301
+ * the parameter name.
302
+ *
303
+ * @param {string} rest - The rest part of a JSDoc parameter string to parse.
304
+ * @returns {[string, boolean]} A tuple where the first element is the name of the parameter,
305
+ * and the second element is a boolean indicating if the parameter is optional.
306
+ */
203
307
  function extractNameAndOptionality(rest) {
204
308
  rest = rest.trim();
205
309
  let optional = false;
310
+ // Examples:
311
+ // name: [kwargs={}] The configuration parameters.
312
+ // name: [d = 1.0] Sample spacing
206
313
  if (rest[0] === '[') {
314
+ // Possible improvement: counting opening/closing brackets for perfect match
207
315
  const closer = rest.lastIndexOf(']');
316
+ // Afterwards name will be: d = 1.0
208
317
  rest = rest.substring(1, closer);
318
+ // mark it for the type:
209
319
  optional = true;
210
320
  }
321
+ // Strip the rest (either leftover of optional value or description)
211
322
  const name = rest.split(' ')[0].split('=')[0].trim();
212
323
  return [name, optional];
213
324
  }
214
325
 
326
+ /**
327
+ * Extracts the content of a string that is delimited by curly braces.
328
+ * @example
329
+ * extractCurlyContent('{ {inner} }'); // Returns: {content: ' {inner} ', nextIndex: 11}
330
+ * @param {string} line - The string to extract from.
331
+ * @returns {{content: string, nextIndex: number}} An object containing the extracted content,
332
+ * and the index of the character immediately following the closing curly brace.
333
+ */
215
334
  function extractCurlyContent(line) {
216
335
  const firstCurly = line.indexOf('{');
217
336
  let k = firstCurly + 1;
@@ -233,6 +352,17 @@ function extractCurlyContent(line) {
233
352
  nextIndex: k + 1
234
353
  };
235
354
  }
355
+ /**
356
+ * Parses JSDoc comments to extract and expand typedefs and their associated properties.
357
+ *
358
+ * It iterates through the lines of a `CommentBlock` from the Babel AST, looking for `@typedef` and `@property`
359
+ * annotations. When it finds a typedef, it stores it in the `typedefs` record. When it finds a property,
360
+ * it adds it to the last found typedef if it is an object type.
361
+ * @param {Record<string, object>} typedefs - An object to store typedefs, mapping type names to their expanded definitions.
362
+ * @param {Console["warn"]} warn - A warn function used for emitting warnings about non-extensible types.
363
+ * @param {import("@babel/types").Comment} comment - A comment extracted from Babel's AST, expected to be a CommentBlock containing type definitions.
364
+ * @param {Function} expandType - A function that takes a type expression as a string and returns a structured representation of the type.
365
+ */
236
366
  function parseJSDocTypedef(typedefs, warn, comment, expandType) {
237
367
  const {
238
368
  type,
@@ -254,13 +384,16 @@ function parseJSDocTypedef(typedefs, warn, comment, expandType) {
254
384
  nextIndex
255
385
  } = extractCurlyContent(line);
256
386
  let name = line.substring(nextIndex).trim();
387
+ // Drop description
257
388
  name = name.split(' ')[0];
258
389
  lastTypedef = expandType(def);
390
+ // Ignore @typedef's that only refer to themselves in another file (see typedef-overwrite test)
259
391
  if (lastTypedef !== name) {
260
392
  typedefs[name] = lastTypedef;
261
393
  }
262
394
  } else if (line.startsWith('@property')) {
263
395
  var _lastTypedef;
396
+ // class @property
264
397
  if (!lastTypedef) {
265
398
  continue;
266
399
  }
@@ -271,6 +404,7 @@ function parseJSDocTypedef(typedefs, warn, comment, expandType) {
271
404
  const rest = line.substring(nextIndex);
272
405
  const propType = expandType(content);
273
406
  const [name, optional] = extractNameAndOptionality(rest);
407
+ // console.log({name, optional, propType});
274
408
  const finalType = simplifyType(propType, optional);
275
409
  if (((_lastTypedef = lastTypedef) == null ? void 0 : _lastTypedef.type) === 'object') {
276
410
  lastTypedef.properties[name] = finalType;
@@ -284,30 +418,62 @@ function parseJSDocTypedef(typedefs, warn, comment, expandType) {
284
418
  }
285
419
  }
286
420
 
421
+ /**
422
+ * @typedef Stat
423
+ * @property {number} checked - How often assertions were added to this kind of node.
424
+ * @property {number} unchecked - How often no assertions were added to this kind of node.
425
+ */
426
+ /**
427
+ * @param {Stat} stat - the Stat.
428
+ */
287
429
  function statReset(stat) {
288
430
  stat.checked = 0;
289
431
  stat.unchecked = 0;
290
432
  }
291
433
 
434
+ /**
435
+ * @example
436
+ * trimEndSpaces('test \n '); // Returns: 'test \n'
437
+ * @param {string} str - The input string.
438
+ * @returns {string} Output string without spaces at the end.
439
+ */
292
440
  function trimEndSpaces(str) {
293
441
  let i = str.length - 1;
442
+ // decrement index while character is a space
294
443
  while (str[i] === ' ') {
295
444
  i--;
296
445
  }
446
+ // return string from 0 to non-space character
297
447
  return str.slice(0, i + 1);
298
448
  }
299
449
 
450
+ /**
451
+ * @typedef {import("@babel/types").Node} Node
452
+ */
453
+ /**
454
+ * Class for converting AST into string.
455
+ */
300
456
  class Stringifier {
301
457
  constructor() {
302
458
  this.forceCurly = false;
459
+ /** @type {Node[]} */
303
460
  this.parents = [];
304
461
  this.lastCommentBlockIndex = -1;
305
462
  this.lastCommentLineIndex = -1;
306
463
  this.numSpaces = 0;
307
464
  }
465
+ /**
466
+ * @param {Node} node - The Babel AST node.
467
+ * @returns {string} Stringification of the node.
468
+ */
308
469
  toSource(node) {
309
470
  var _node$extra;
471
+ // handle this case only temporarily
310
472
  if (node === null) {
473
+ // Contexts like:
474
+ // Object.assign(Channel3d.prototype, {
475
+ // setPosition: function /*null*/(position) {
476
+ //return '/*null*/';
311
477
  return '';
312
478
  }
313
479
  const {
@@ -321,6 +487,7 @@ class Stringifier {
321
487
  let out = '';
322
488
  let comments = '';
323
489
  if (leadingComments) {
490
+ // comments += this.mapToSource(leadingComments).join('');
324
491
  comments = this.leadingCommentsToSource(leadingComments);
325
492
  }
326
493
  if (node != null && (_node$extra = node.extra) != null && _node$extra.parenthesized) {
@@ -331,9 +498,17 @@ class Stringifier {
331
498
  if (trailingComments) {
332
499
  out += this.trailingCommentsToSource(trailingComments);
333
500
  }
501
+ // leadingComments and trailingComments lead to duplicates, not always tho
502
+ //if (node && node.trailingComments) {
503
+ // out += this.mapToSource(node.trailingComments).join('\n') + '\n';
504
+ //}
334
505
  this.parents.pop();
335
506
  return out;
336
507
  }
508
+ /**
509
+ * @param {Node} node - The Babel AST node.
510
+ * @returns {string} Stringification of the node.
511
+ */
337
512
  toSource_(node) {
338
513
  if (!node) {
339
514
  return '';
@@ -356,12 +531,18 @@ class Stringifier {
356
531
  return `rtiUnhandled("${node.type}");\n`;
357
532
  }
358
533
  parentProvidesSpaces(reverseIndex) {
534
+ // May look like: .../ArrayExpression/StringLiteral/CommentLine
359
535
  const {
360
536
  parents
361
537
  } = this;
362
538
  const parent = parents[parents.length - reverseIndex];
539
+ // console.log("Got parent", parent.type);
363
540
  return (parent == null ? void 0 : parent.type) === 'ArrayExpression';
364
541
  }
542
+ /**
543
+ * @param {import("@babel/types").CommentBlock} node - The Babel AST node.
544
+ * @returns {string} Stringification of the node.
545
+ */
365
546
  CommentBlock(node) {
366
547
  const {
367
548
  loc
@@ -370,6 +551,7 @@ class Stringifier {
370
551
  value
371
552
  } = node;
372
553
  if (this.lastCommentBlockIndex === loc.start.index) {
554
+ // console.log("CommentBlock> ignore double");
373
555
  return '';
374
556
  }
375
557
  this.lastCommentBlockIndex = loc.start.index;
@@ -377,6 +559,7 @@ class Stringifier {
377
559
  spaces
378
560
  } = this;
379
561
  let out = '';
562
+ /** @todo add option for number-of-spaces */
380
563
  const dedicatedLine = spaces.length === loc.start.column;
381
564
  if (dedicatedLine) {
382
565
  out += spaces;
@@ -384,6 +567,7 @@ class Stringifier {
384
567
  const multiLine = loc.start.line !== loc.end.line;
385
568
  if (multiLine) {
386
569
  var _this$parents$at;
570
+ // console.log(this.parents.at(-2)?.type === 'AssignmentExpression');
387
571
  if (((_this$parents$at = this.parents.at(-2)) == null ? void 0 : _this$parents$at.type) === 'AssignmentExpression') {
388
572
  spaces += ' ';
389
573
  }
@@ -391,7 +575,11 @@ class Stringifier {
391
575
  }
392
576
  out += '/*';
393
577
  if (value.includes('\n')) {
394
- value = trimEndSpaces(value.replace(/^\s*\*/gm, '*')).split('\n').filter(_ => _.length).map((line, i) => (i ? spaces + ' ' : '') + line).join('\n');
578
+ // A bit tricky to handle multiline comments,
579
+ // we have to remove the given indentation level.
580
+ value = trimEndSpaces(value.replace(/^\s*\*/gm, '*')).split('\n').filter(_ => _.length) // skip empty lines
581
+ // Skip spaces in first line for /**
582
+ .map((line, i) => (i ? spaces + ' ' : '') + line).join('\n');
395
583
  out += `${value}\n${spaces} */`;
396
584
  } else {
397
585
  out += `${value}*/`;
@@ -403,12 +591,17 @@ class Stringifier {
403
591
  }
404
592
  return out;
405
593
  }
594
+ /**
595
+ * @param {import("@babel/types").CommentLine} node - The Babel AST node.
596
+ * @returns {string} Stringification of the node.
597
+ */
406
598
  CommentLine(node) {
407
599
  const {
408
600
  value,
409
601
  loc
410
602
  } = node;
411
603
  if (this.lastCommentLineIndex === loc.start.index) {
604
+ // console.log("CommentLine> ignore double");
412
605
  return '';
413
606
  }
414
607
  this.lastCommentLineIndex = loc.start.index;
@@ -430,6 +623,8 @@ class Stringifier {
430
623
  }
431
624
  leadingCommentsToSource(leadingComments) {
432
625
  let out = leadingComments.map(_ => this.commentToSource(_, 'leading')).join('');
626
+ // The comments "took" the spaces, which e.g. ArrayExpression added,
627
+ // so we have to add them back now for parents that provide spaces.
433
628
  if (this.parentProvidesSpaces(2)) {
434
629
  out += this.spaces;
435
630
  }
@@ -443,6 +638,7 @@ class Stringifier {
443
638
  loc
444
639
  } = trailingComment;
445
640
  const dedicatedLine = this.spaces.length === loc.start.column;
641
+ // out += ` /*dedicatedLine=${dedicatedLine}*/ `;
446
642
  if (dedicatedLine) {
447
643
  out += '\n';
448
644
  } else {
@@ -453,8 +649,14 @@ class Stringifier {
453
649
  }
454
650
  return out;
455
651
  }
652
+ /**
653
+ * @param {*} comment - The comment "node" (not a real Babel Node node though, hence this special treatment)
654
+ * @param {'leading'|'trailing'} pos - The position, either 'leading' or 'trailing'.
655
+ * @returns {string} The comment "node" as a string.
656
+ */
456
657
  commentToSource(comment, pos) {
457
658
  if (comment.type === 'CommentBlock') {
659
+ //if (pos === 'leading') {}
458
660
  return this.CommentBlock(comment);
459
661
  } else if (comment.type === 'CommentLine') {
460
662
  return this.CommentLine(comment);
@@ -463,6 +665,12 @@ class Stringifier {
463
665
  debugger;
464
666
  return '';
465
667
  }
668
+ /**
669
+ * Only add { and } when requested (e.g. for Asserter).
670
+ * showAST("if (true) 2;") vs showAST("if (true) {2}")
671
+ * @param {Node} node - The Babel AST node.
672
+ * @returns {string} Stringification of the node.
673
+ */
466
674
  toSourceCurly(node) {
467
675
  if (!this.forceCurly) {
468
676
  return this.toSource(node);
@@ -475,7 +683,10 @@ class Stringifier {
475
683
  let out = '';
476
684
  if (needCurly) {
477
685
  out += ' {\n';
686
+ //out += spaces + `/* toSourceCurly> needCurly=${needCurly} type=${type}
687
+ //parents=${parents.map(_=>_.type).join('->')}*/\n`;
478
688
  }
689
+
479
690
  out += this.toSource(node);
480
691
  if (needCurly) {
481
692
  out += '\n';
@@ -495,12 +706,29 @@ class Stringifier {
495
706
  const t = this.parentType;
496
707
  return t !== "ForStatement" && t !== "ForInStatement" && t !== "ForOfStatement";
497
708
  }
709
+ /**
710
+ * @param {Node[]} arr - Array of nodes to convert.
711
+ * @returns {string[]} Array of strings for each node.
712
+ */
498
713
  mapToSource(arr) {
499
714
  return arr.map(_ => this.toSource(_));
500
715
  }
716
+ /**
717
+ * Generates a string representing type checks for a given Babel AST node.
718
+ *
719
+ * Note: This method serves as a stub and should be overridden in subclasses.
720
+ * The actual implementation is expected to be provided in Asserter.mjs, where
721
+ * it would create runtime type assertions based on the AST node provided.
722
+ *
723
+ * @param {Node} node - The Babel AST node for which to generate type checks.
724
+ * @returns {string} A placeholder string, as this stub implementation does nothing; expected to be overridden.
725
+ */
501
726
  generateTypeChecks(node) {
502
727
  return '';
503
728
  }
729
+ /**
730
+ * @type {string} A string of two spaces per indentation.
731
+ */
504
732
  get spaces() {
505
733
  return ' '.repeat(this.numSpaces);
506
734
  }
@@ -513,18 +741,32 @@ class Stringifier {
513
741
  const path = parents.map(_ => _.type).join('/');
514
742
  return `${spaces}// got ${n} spaces for ${path}\n`;
515
743
  }
744
+ /**
745
+ * > await something();
746
+ *
747
+ * @param {import("@babel/types").AwaitExpression} node - The Babel AST node.
748
+ * @returns {string} Stringification of the node.
749
+ */
516
750
  AwaitExpression(node) {
517
751
  const {
518
752
  argument
519
753
  } = node;
520
754
  return `await ${this.toSource(argument)}`;
521
755
  }
756
+ /**
757
+ * @param {import("@babel/types").ClassBody} node - The Babel AST node.
758
+ * @returns {string} Stringification of the node.
759
+ */
522
760
  ClassBody(node) {
523
761
  const {
524
762
  body
525
763
  } = node;
526
764
  return this.mapToSource(body).join('\n');
527
765
  }
766
+ /**
767
+ * @param {import("@babel/types").ClassMethod} node - The Babel AST node.
768
+ * @returns {string} Stringification of the node.
769
+ */
528
770
  ClassMethod(node) {
529
771
  const {
530
772
  static: static_,
@@ -566,6 +808,10 @@ class Stringifier {
566
808
  out += this.toSource(body);
567
809
  return out;
568
810
  }
811
+ /**
812
+ * @param {import("@babel/types").ClassExpression} node - The Babel AST node.
813
+ * @returns {string} Stringification of the node.
814
+ */
569
815
  ClassExpression(node) {
570
816
  const {
571
817
  id,
@@ -574,6 +820,7 @@ class Stringifier {
574
820
  } = node;
575
821
  let out = 'class ';
576
822
  if (id !== null) {
823
+ // Babel: strange API, either null or undefined... pick a type, maybe oversight/bug
577
824
  out += this.toSource(id) + ' ';
578
825
  }
579
826
  if (superClass !== null) {
@@ -584,6 +831,10 @@ class Stringifier {
584
831
  out += `{\n${c}\n}`;
585
832
  return out;
586
833
  }
834
+ /**
835
+ * @param {import("@babel/types").ClassDeclaration} node - The Babel AST node.
836
+ * @returns {string} Stringification of the node.
837
+ */
587
838
  ClassDeclaration(node) {
588
839
  const {
589
840
  id,
@@ -605,6 +856,10 @@ class Stringifier {
605
856
  out += '\n}\n';
606
857
  return out;
607
858
  }
859
+ /**
860
+ * @param {import("@babel/types").ClassPrivateMethod} node - The Babel AST node.
861
+ * @returns {string} Stringification of the node.
862
+ */
608
863
  ClassPrivateMethod(node) {
609
864
  const {
610
865
  static: static_,
@@ -641,6 +896,15 @@ class Stringifier {
641
896
  out += this.toSource(body);
642
897
  return out;
643
898
  }
899
+ /**
900
+ * > asd;
901
+ * > asd = 1;
902
+ * > static asd;
903
+ * > static asd = 1;
904
+ *
905
+ * @param {import("@babel/types").ClassProperty} node - The Babel AST node.
906
+ * @returns {string} Stringification of the node.
907
+ */
644
908
  ClassProperty(node) {
645
909
  const {
646
910
  key,
@@ -648,7 +912,7 @@ class Stringifier {
648
912
  value,
649
913
  leadingComments
650
914
  } = node;
651
- const static_ = node.static;
915
+ const static_ = node.static; // JS keyword is problematic in object destructuring
652
916
  const a = this.toSource(key);
653
917
  const b = this.toSource(value);
654
918
  let out = this.spaces;
@@ -666,6 +930,10 @@ class Stringifier {
666
930
  out += ';';
667
931
  return out;
668
932
  }
933
+ /**
934
+ * @param {import("@babel/types").ClassPrivateProperty} node - The Babel AST node.
935
+ * @returns {string} Stringification of the node.
936
+ */
669
937
  ClassPrivateProperty(node) {
670
938
  const {
671
939
  static: static_,
@@ -683,6 +951,10 @@ class Stringifier {
683
951
  out += ';';
684
952
  return out;
685
953
  }
954
+ /**
955
+ * @param {import("@babel/types").ContinueStatement} node - The Babel AST node.
956
+ * @returns {string} Stringification of the node.
957
+ */
686
958
  ContinueStatement(node) {
687
959
  const {
688
960
  label
@@ -694,9 +966,22 @@ class Stringifier {
694
966
  out += ';';
695
967
  return out;
696
968
  }
969
+ /**
970
+ * Converts an array of Babel AST nodes representing function parameters into a comma-separated string.
971
+ *
972
+ * Each parameter node is converted to its source representation and combined into a single
973
+ * string suitable for inserting into a function declaration's parentheses.
974
+ *
975
+ * @param {Node[]} params - An array of Babel AST nodes representing the function parameters to be stringified.
976
+ * @returns {string} A string representing the serialized parameters, enclosed in parentheses.
977
+ */
697
978
  FunctionDeclarationParams(params) {
698
979
  return '(' + this.mapToSource(params).join(', ') + ')';
699
980
  }
981
+ /**
982
+ * @param {import("@babel/types").FunctionDeclaration} node - The Babel AST node.
983
+ * @returns {string} Stringification of the node.
984
+ */
700
985
  FunctionDeclaration(node) {
701
986
  const {
702
987
  async,
@@ -718,13 +1003,18 @@ class Stringifier {
718
1003
  out += this.toSource(body);
719
1004
  return out;
720
1005
  }
1006
+ /**
1007
+ * @param {import("@babel/types").FunctionExpression} node - The Babel AST node.
1008
+ * @returns {string} Stringification of the node.
1009
+ */
721
1010
  FunctionExpression(node) {
1011
+ // Leading comments for asd=function(){} are in ExpressionStatement
722
1012
  const {
723
1013
  async,
724
1014
  body,
725
1015
  generator,
726
- id,
727
- params
1016
+ id /*, leadingComments*/,
1017
+ params /*, extra*/
728
1018
  } = node;
729
1019
  let out = '';
730
1020
  if (node.leadingComments) {
@@ -744,12 +1034,16 @@ class Stringifier {
744
1034
  }
745
1035
  return out;
746
1036
  }
1037
+ /**
1038
+ * @param {import("@babel/types").ArrowFunctionExpression} node - The Babel AST node.
1039
+ * @returns {string} Stringification of the node.
1040
+ */
747
1041
  ArrowFunctionExpression(node) {
748
1042
  const {
749
1043
  async,
750
1044
  body,
751
1045
  generator,
752
- params
1046
+ params /*, extra*/
753
1047
  } = node;
754
1048
  let out = '';
755
1049
  if (async) {
@@ -763,6 +1057,10 @@ class Stringifier {
763
1057
  out += this.toSource(body);
764
1058
  return out;
765
1059
  }
1060
+ /**
1061
+ * @param {import("@babel/types").BigIntLiteral} node - The Babel AST node.
1062
+ * @returns {string} Stringification of the node.
1063
+ */
766
1064
  BigIntLiteral(node) {
767
1065
  const {
768
1066
  extra,
@@ -770,6 +1068,10 @@ class Stringifier {
770
1068
  } = node;
771
1069
  return extra.raw;
772
1070
  }
1071
+ /**
1072
+ * @param {import("@babel/types").BlockStatement} node - The Babel AST node.
1073
+ * @returns {string} Stringification of the node.
1074
+ */
773
1075
  BlockStatement(node) {
774
1076
  const {
775
1077
  body,
@@ -779,6 +1081,7 @@ class Stringifier {
779
1081
  let out = '';
780
1082
  out += ' {';
781
1083
  this.numSpaces++;
1084
+ // Handle Directive/DirectiveLiteral like 'use strict';
782
1085
  if (directives && directives.length) {
783
1086
  out += '\n';
784
1087
  out += this.mapToSource(directives).join('\n') + '\n';
@@ -786,8 +1089,10 @@ class Stringifier {
786
1089
  out += this.generateTypeChecks(node);
787
1090
  if (body.length) {
788
1091
  if (out.length === 2) {
1092
+ // 2 is ' {'
789
1093
  out += '\n';
790
1094
  }
1095
+ // out += '/*'+out.length+'*/';
791
1096
  out += this.mapToSource(body).join('\n') + '\n';
792
1097
  out += spaces;
793
1098
  }
@@ -795,12 +1100,24 @@ class Stringifier {
795
1100
  out += '}';
796
1101
  return out;
797
1102
  }
1103
+ /**
1104
+ * > 'use strict';
1105
+ *
1106
+ * @param {import("@babel/types").Directive} node - The Babel AST node.
1107
+ * @returns {string} Stringification of the node.
1108
+ */
798
1109
  Directive(node) {
799
1110
  const {
800
1111
  value
801
1112
  } = node;
802
1113
  return this.toSource(value);
803
1114
  }
1115
+ /**
1116
+ * > 'use strict';
1117
+ *
1118
+ * @param {import("@babel/types").DirectiveLiteral} node - The Babel AST node.
1119
+ * @returns {string} Stringification of the node.
1120
+ */
804
1121
  DirectiveLiteral(node) {
805
1122
  const {
806
1123
  extra,
@@ -811,6 +1128,10 @@ class Stringifier {
811
1128
  } = this;
812
1129
  return `${spaces}${extra.raw};`;
813
1130
  }
1131
+ /**
1132
+ * @param {import("@babel/types").ReturnStatement} node - The Babel AST node.
1133
+ * @returns {string} Stringification of the node.
1134
+ */
814
1135
  ReturnStatement(node) {
815
1136
  const {
816
1137
  argument
@@ -823,9 +1144,17 @@ class Stringifier {
823
1144
  }
824
1145
  return spaces + 'return ' + this.toSource(argument) + ';';
825
1146
  }
1147
+ /**
1148
+ * @param {import("@babel/types").Identifier} node - The Babel AST node.
1149
+ * @returns {string} Stringification of the node.
1150
+ */
826
1151
  Identifier(node) {
827
1152
  return node.name;
828
1153
  }
1154
+ /**
1155
+ * @param {import("@babel/types").IfStatement} node - The Babel AST node.
1156
+ * @returns {string} Stringification of the node.
1157
+ */
829
1158
  IfStatement(node) {
830
1159
  const {
831
1160
  alternate,
@@ -834,7 +1163,10 @@ class Stringifier {
834
1163
  } = node;
835
1164
  const spaces = this.spaces;
836
1165
  let out = '';
1166
+ //if (this.parentType !== "IfStatement")
1167
+ //{
837
1168
  out += spaces;
1169
+ //}
838
1170
  out += `if (${this.toSource(test)})`;
839
1171
  out += this.toSourceCurly(consequent);
840
1172
  if (alternate) {
@@ -842,6 +1174,10 @@ class Stringifier {
842
1174
  }
843
1175
  return out;
844
1176
  }
1177
+ /**
1178
+ * @param {import("@babel/types").LabeledStatement} node - The Babel AST node.
1179
+ * @returns {string} Stringification of the node.
1180
+ */
845
1181
  LabeledStatement(node) {
846
1182
  const {
847
1183
  body,
@@ -856,6 +1192,15 @@ class Stringifier {
856
1192
  out += spaces + this.toSource(body);
857
1193
  return out;
858
1194
  }
1195
+ /**
1196
+ * @example
1197
+ * ts = require("typescript");
1198
+ * ts.createSourceFile("repl.ts", "a = 1", ts.ScriptTarget.Latest);
1199
+ * ts.createSourceFile("repl.ts", "!!(a = 1 + 2)", ts.ScriptTarget.Latest);
1200
+ * showAST('!!(a = 1)');
1201
+ * @param {import("@babel/types").UnaryExpression} node - The Babel AST node.
1202
+ * @returns {string} Stringification of the node.
1203
+ */
859
1204
  UnaryExpression(node) {
860
1205
  let out = '';
861
1206
  const {
@@ -866,6 +1211,7 @@ class Stringifier {
866
1211
  if (!prefix) {
867
1212
  console.warn("Stringifier#UnaryExpression> never considered !prefix", node);
868
1213
  }
1214
+ // Add a space for: typeof/delete/void
869
1215
  const c = operator.charCodeAt(0);
870
1216
  let op = operator;
871
1217
  if (c >= 97 && c <= 122) {
@@ -875,6 +1221,10 @@ class Stringifier {
875
1221
  out += this.toSource(argument);
876
1222
  return out;
877
1223
  }
1224
+ /**
1225
+ * @param {import("@babel/types").MemberExpression} node - The Babel AST node.
1226
+ * @returns {string} Stringification of the node.
1227
+ */
878
1228
  MemberExpression(node) {
879
1229
  const {
880
1230
  computed,
@@ -886,6 +1236,10 @@ class Stringifier {
886
1236
  }
887
1237
  return `${this.toSource(object)}.${this.toSource(property)}`;
888
1238
  }
1239
+ /**
1240
+ * @param {import("@babel/types").MetaProperty} node - The Babel AST node.
1241
+ * @returns {string} Stringification of the node.
1242
+ */
889
1243
  MetaProperty(node) {
890
1244
  const {
891
1245
  meta,
@@ -895,12 +1249,26 @@ class Stringifier {
895
1249
  const rhs = this.toSource(property);
896
1250
  return `${lhs}.${rhs}`;
897
1251
  }
1252
+ /**
1253
+ * @param {import("@babel/types").ExpressionStatement} node - The Babel AST node.
1254
+ * @returns {string} Stringification of the node.
1255
+ */
898
1256
  ExpressionStatement(node) {
899
1257
  const {
900
1258
  expression
901
1259
  } = node;
1260
+ /**
1261
+ * Not just a fall-through, adds a semicolon when it has semantic meaning.
1262
+ * E.g. invalid:
1263
+ * (function() {})()
1264
+ * (function() {})()
1265
+ */
902
1266
  return this.spaces + this.toSource(expression) + ';';
903
1267
  }
1268
+ /**
1269
+ * @param {import("@babel/types").CallExpression} node - The Babel AST node.
1270
+ * @returns {string} Stringification of the node.
1271
+ */
904
1272
  CallExpression(node) {
905
1273
  const {
906
1274
  callee,
@@ -916,6 +1284,14 @@ class Stringifier {
916
1284
  out += ")";
917
1285
  return out;
918
1286
  }
1287
+ /**
1288
+ * We replicate the exact AST for validation:
1289
+ * x = 1;
1290
+ * y = {x,}
1291
+ * z = {y};
1292
+ * @param {import("@babel/types").ObjectExpression} node - The Babel AST node.
1293
+ * @returns {string} Stringification of the node.
1294
+ */
919
1295
  ObjectExpression(node) {
920
1296
  const {
921
1297
  properties,
@@ -937,6 +1313,10 @@ class Stringifier {
937
1313
  out += '}';
938
1314
  return out;
939
1315
  }
1316
+ /**
1317
+ * @param {import("@babel/types").ObjectProperty} node - The Babel AST node.
1318
+ * @returns {string} Stringification of the node.
1319
+ */
940
1320
  ObjectProperty(node) {
941
1321
  const {
942
1322
  computed,
@@ -964,9 +1344,17 @@ class Stringifier {
964
1344
  out += right;
965
1345
  return out;
966
1346
  }
1347
+ /**
1348
+ * @param {import("@babel/types").BooleanLiteral} node - The Babel AST node.
1349
+ * @returns {string} Stringification of the node.
1350
+ */
967
1351
  BooleanLiteral(node) {
968
1352
  return node.value.toString();
969
1353
  }
1354
+ /**
1355
+ * @param {import("@babel/types").AssignmentExpression} node - The Babel AST node.
1356
+ * @returns {string} Stringification of the node.
1357
+ */
970
1358
  AssignmentExpression(node) {
971
1359
  const {
972
1360
  left,
@@ -975,8 +1363,13 @@ class Stringifier {
975
1363
  } = node;
976
1364
  const left_ = this.toSource(left);
977
1365
  const right_ = this.toSource(right);
1366
+ // operator is for example: = |= &=
978
1367
  return `${left_} ${operator} ${right_}`;
979
1368
  }
1369
+ /**
1370
+ * @param {import("@babel/types").BinaryExpression} node - The Babel AST node.
1371
+ * @returns {string} Stringification of the node.
1372
+ */
980
1373
  BinaryExpression(node) {
981
1374
  const {
982
1375
  left,
@@ -987,9 +1380,17 @@ class Stringifier {
987
1380
  const right_ = this.toSource(right);
988
1381
  return `${left_} ${operator} ${right_}`;
989
1382
  }
1383
+ /**
1384
+ * @param {import("@babel/types").ThisExpression} node - The Babel AST node.
1385
+ * @returns {string} Stringification of the node.
1386
+ */
990
1387
  ThisExpression(node) {
991
1388
  return 'this';
992
1389
  }
1390
+ /**
1391
+ * @param {import("@babel/types").ArrayExpression} node - The Babel AST node.
1392
+ * @returns {string} Stringification of the node.
1393
+ */
993
1394
  ArrayExpression(node) {
994
1395
  const {
995
1396
  elements,
@@ -1012,6 +1413,10 @@ class Stringifier {
1012
1413
  out += '\n' + this.spaces + ']';
1013
1414
  return out;
1014
1415
  }
1416
+ /**
1417
+ * @param {import("@babel/types").VariableDeclaration} node - The Babel AST node.
1418
+ * @returns {string} Stringification of the node.
1419
+ */
1015
1420
  VariableDeclaration(node) {
1016
1421
  const {
1017
1422
  declarations,
@@ -1025,6 +1430,10 @@ class Stringifier {
1025
1430
  }
1026
1431
  return `${spaces}${kind} ${this.mapToSource(declarations).join(', ')}${semicolon}`;
1027
1432
  }
1433
+ /**
1434
+ * @param {import("@babel/types").VariableDeclarator} node - The Babel AST node.
1435
+ * @returns {string} Stringification of the node.
1436
+ */
1028
1437
  VariableDeclarator(node) {
1029
1438
  const {
1030
1439
  id,
@@ -1035,6 +1444,10 @@ class Stringifier {
1035
1444
  }
1036
1445
  return this.toSource(id);
1037
1446
  }
1447
+ /**
1448
+ * @param {import("@babel/types").ConditionalExpression} node - The Babel AST node.
1449
+ * @returns {string} Stringification of the node.
1450
+ */
1038
1451
  ConditionalExpression(node) {
1039
1452
  const {
1040
1453
  alternate,
@@ -1043,6 +1456,12 @@ class Stringifier {
1043
1456
  } = node;
1044
1457
  return `${this.toSource(test)} ? ${this.toSource(consequent)} : ${this.toSource(alternate)}`;
1045
1458
  }
1459
+ /**
1460
+ * if (...)
1461
+ *
1462
+ * @param {import("@babel/types").LogicalExpression} node - The Babel AST node.
1463
+ * @returns {string} Stringification of the node.
1464
+ */
1046
1465
  LogicalExpression(node) {
1047
1466
  const {
1048
1467
  left,
@@ -1059,6 +1478,12 @@ class Stringifier {
1059
1478
  }
1060
1479
  return `${l} ${operator} ${r}`;
1061
1480
  }
1481
+ /**
1482
+ * TODO TEST: can init be undefined in for(;;)
1483
+ *
1484
+ * @param {import("@babel/types").ForStatement} node - The Babel AST node.
1485
+ * @returns {string} Stringification of the node.
1486
+ */
1062
1487
  ForStatement(node) {
1063
1488
  const {
1064
1489
  init,
@@ -1073,6 +1498,12 @@ class Stringifier {
1073
1498
  const b = this.toSourceCurly(body);
1074
1499
  return `${spaces}for (${i}; ${t}; ${u})${b}`;
1075
1500
  }
1501
+ /**
1502
+ * showAST("++i")
1503
+ *
1504
+ * @param {import("@babel/types").UpdateExpression} node - The Babel AST node.
1505
+ * @returns {string} Stringification of the node.
1506
+ */
1076
1507
  UpdateExpression(node) {
1077
1508
  const {
1078
1509
  operator,
@@ -1084,6 +1515,10 @@ class Stringifier {
1084
1515
  }
1085
1516
  return `${this.toSource(argument)}${operator}`;
1086
1517
  }
1518
+ /**
1519
+ * @param {import("@babel/types").NewExpression} node - The Babel AST node.
1520
+ * @returns {string} Stringification of the node.
1521
+ */
1087
1522
  NewExpression(node) {
1088
1523
  const {
1089
1524
  callee,
@@ -1093,6 +1528,12 @@ class Stringifier {
1093
1528
  const args = this.mapToSource(args_).join(', ');
1094
1529
  return `new ${c}(${args})`;
1095
1530
  }
1531
+ /**
1532
+ * @example
1533
+ * console.log(ast2json(parseSync("`${1} b ${2+3}c`").program.body[0]));
1534
+ * @param {import("@babel/types").TemplateLiteral} node - The Babel AST node.
1535
+ * @returns {string} Stringification of the node.
1536
+ */
1096
1537
  TemplateLiteral(node) {
1097
1538
  const {
1098
1539
  expressions,
@@ -1108,12 +1549,20 @@ class Stringifier {
1108
1549
  out += '`';
1109
1550
  return out;
1110
1551
  }
1552
+ /**
1553
+ * @param {import("@babel/types").TemplateElement} node - The Babel AST node.
1554
+ * @returns {string} Stringification of the node.
1555
+ */
1111
1556
  TemplateElement(node) {
1112
1557
  const {
1113
1558
  value
1114
1559
  } = node;
1115
1560
  return value.raw;
1116
1561
  }
1562
+ /**
1563
+ * @param {import("@babel/types").ParenthesizedExpression} node - The Babel AST node.
1564
+ * @returns {string} Stringification of the node.
1565
+ */
1117
1566
  ParenthesizedExpression(node) {
1118
1567
  const {
1119
1568
  expression,
@@ -1130,21 +1579,37 @@ class Stringifier {
1130
1579
  out += '\n' + this.spaces + ')';
1131
1580
  return out;
1132
1581
  }
1582
+ /**
1583
+ * @param {import("@babel/types").PrivateName} node - The Babel AST node.
1584
+ * @returns {string} Stringification of the node.
1585
+ */
1133
1586
  PrivateName(node) {
1134
1587
  const {
1135
1588
  id
1136
1589
  } = node;
1137
1590
  return `#${this.toSource(id)}`;
1138
1591
  }
1592
+ /**
1593
+ * @param {import("@babel/types").ExportAllDeclaration} node - The Babel AST node.
1594
+ * @returns {string} Stringification of the node.
1595
+ */
1139
1596
  ExportAllDeclaration(node) {
1140
1597
  return `export * from ${node.source.extra.raw};`;
1141
1598
  }
1599
+ /**
1600
+ * @param {import("@babel/types").ExportNamespaceSpecifier} node - The Babel AST node.
1601
+ * @returns {string} Stringification of the node.
1602
+ */
1142
1603
  ExportNamespaceSpecifier(node) {
1143
1604
  const {
1144
1605
  exported
1145
1606
  } = node;
1146
1607
  return '* as ' + this.toSource(exported);
1147
1608
  }
1609
+ /**
1610
+ * @param {import("@babel/types").ExportNamedDeclaration} node - The Babel AST node.
1611
+ * @returns {string} Stringification of the node.
1612
+ */
1148
1613
  ExportNamedDeclaration(node) {
1149
1614
  const {
1150
1615
  declaration,
@@ -1171,6 +1636,7 @@ class Stringifier {
1171
1636
  out += '}';
1172
1637
  } else {
1173
1638
  out += 'export {\n';
1639
+ // todo: fix spacing system
1174
1640
  out += this.mapToSource(specifiers).map(_ => ` ${this.spaces}${_}`).join(',\n');
1175
1641
  out += '\n';
1176
1642
  out += this.spaces;
@@ -1188,6 +1654,10 @@ class Stringifier {
1188
1654
  }
1189
1655
  return out;
1190
1656
  }
1657
+ /**
1658
+ * @param {import("@babel/types").ExportDefaultDeclaration} node - The Babel AST node.
1659
+ * @returns {string} Stringification of the node.
1660
+ */
1191
1661
  ExportDefaultDeclaration(node) {
1192
1662
  const {
1193
1663
  declaration
@@ -1195,6 +1665,10 @@ class Stringifier {
1195
1665
  const a = this.toSource(declaration);
1196
1666
  return `export default ${a};`;
1197
1667
  }
1668
+ /**
1669
+ * @param {import("@babel/types").ExportSpecifier} node - The Babel AST node.
1670
+ * @returns {string} Stringification of the node.
1671
+ */
1198
1672
  ExportSpecifier(node) {
1199
1673
  const {
1200
1674
  exported,
@@ -1208,11 +1682,22 @@ class Stringifier {
1208
1682
  if (left === right) {
1209
1683
  return `${spaces}${left}`;
1210
1684
  }
1685
+ // For example:
1686
+ // PostEffect: PostEffect$1\n
1687
+ // createMesh: createMesh$1\n
1211
1688
  return `${spaces}${right} as ${left}`;
1212
1689
  }
1690
+ /**
1691
+ * @param {import("@babel/types").Super} node - The Babel AST node.
1692
+ * @returns {string} Stringification of the node.
1693
+ */
1213
1694
  Super(node) {
1214
1695
  return 'super';
1215
1696
  }
1697
+ /**
1698
+ * @param {import("@babel/types").ForInStatement} node - The Babel AST node.
1699
+ * @returns {string} Stringification of the node.
1700
+ */
1216
1701
  ForInStatement(node) {
1217
1702
  const {
1218
1703
  left,
@@ -1227,6 +1712,10 @@ class Stringifier {
1227
1712
  const b = this.toSourceCurly(body);
1228
1713
  return `${spaces}for (${l} in ${r})${b}`;
1229
1714
  }
1715
+ /**
1716
+ * @param {import("@babel/types").ThrowStatement} node - The Babel AST node.
1717
+ * @returns {string} Stringification of the node.
1718
+ */
1230
1719
  ThrowStatement(node) {
1231
1720
  const {
1232
1721
  argument
@@ -1237,6 +1726,10 @@ class Stringifier {
1237
1726
  const arg = this.toSource(argument);
1238
1727
  return `${spaces}throw ${arg};`;
1239
1728
  }
1729
+ /**
1730
+ * @param {import("@babel/types").WhileStatement} node - The Babel AST node.
1731
+ * @returns {string} Stringification of the node.
1732
+ */
1240
1733
  WhileStatement(node) {
1241
1734
  const {
1242
1735
  test,
@@ -1249,6 +1742,10 @@ class Stringifier {
1249
1742
  const b = this.toSourceCurly(body);
1250
1743
  return `${spaces}while (${t})${b}`;
1251
1744
  }
1745
+ /**
1746
+ * @param {import("@babel/types").BreakStatement} node - The Babel AST node.
1747
+ * @returns {string} Stringification of the node.
1748
+ */
1252
1749
  BreakStatement(node) {
1253
1750
  const {
1254
1751
  label
@@ -1260,6 +1757,10 @@ class Stringifier {
1260
1757
  out += ';';
1261
1758
  return out;
1262
1759
  }
1760
+ /**
1761
+ * @param {import("@babel/types").ForOfStatement} node - The Babel AST node.
1762
+ * @returns {string} Stringification of the node.
1763
+ */
1263
1764
  ForOfStatement(node) {
1264
1765
  const {
1265
1766
  await: await_,
@@ -1276,6 +1777,13 @@ class Stringifier {
1276
1777
  const a = await_ ? 'await ' : '';
1277
1778
  return `${spaces}for ${a}(${l} of ${r})${b}`;
1278
1779
  }
1780
+ /**
1781
+ * for (const [a, b] in c)
1782
+ * --> [a, b] is the array pattern
1783
+ *
1784
+ * @param {import("@babel/types").ArrayPattern} node - The Babel AST node.
1785
+ * @returns {string} Stringification of the node.
1786
+ */
1279
1787
  ArrayPattern(node) {
1280
1788
  const {
1281
1789
  elements
@@ -1283,6 +1791,10 @@ class Stringifier {
1283
1791
  const e = this.mapToSource(elements).join(', ');
1284
1792
  return `[${e}]`;
1285
1793
  }
1794
+ /**
1795
+ * @param {import("@babel/types").SwitchStatement} node - The Babel AST node.
1796
+ * @returns {string} Stringification of the node.
1797
+ */
1286
1798
  SwitchStatement(node) {
1287
1799
  const {
1288
1800
  discriminant,
@@ -1297,6 +1809,10 @@ class Stringifier {
1297
1809
  out += spaces + '}';
1298
1810
  return out;
1299
1811
  }
1812
+ /**
1813
+ * @param {import("@babel/types").SwitchCase} node - The Babel AST node.
1814
+ * @returns {string} Stringification of the node.
1815
+ */
1300
1816
  SwitchCase(node) {
1301
1817
  const {
1302
1818
  consequent,
@@ -1312,7 +1828,13 @@ class Stringifier {
1312
1828
  }
1313
1829
  return `${spaces}default:\n${c}`;
1314
1830
  }
1831
+ /**
1832
+ * @param {import("@babel/types").RegExpLiteral} node - The Babel AST node.
1833
+ * @returns {string} Stringification of the node.
1834
+ */
1315
1835
  RegExpLiteral(node) {
1836
+ // parseSync("/test/gm")
1837
+ // todo figure out why it always exposes node.value === undefined... oversight in Babel?
1316
1838
  const {
1317
1839
  extra,
1318
1840
  value,
@@ -1321,15 +1843,32 @@ class Stringifier {
1321
1843
  } = node;
1322
1844
  return extra.raw;
1323
1845
  }
1846
+ /**
1847
+ * for (var i=0, n=arr.length; i<n; i++)
1848
+ * sequence expression: var i=0, n=arr.length
1849
+ *
1850
+ * @param {import("@babel/types").SequenceExpression} node - The Babel AST node.
1851
+ * @returns {string} Stringification of the node.
1852
+ */
1324
1853
  SequenceExpression(node) {
1325
1854
  const {
1326
1855
  expressions
1327
1856
  } = node;
1328
1857
  return this.mapToSource(expressions).join(', ');
1329
1858
  }
1859
+ /**
1860
+ * showAST("if (true);");
1861
+ *
1862
+ * @param {import("@babel/types").EmptyStatement} node - The Babel AST node.
1863
+ * @returns {string} Stringification of the node.
1864
+ */
1330
1865
  EmptyStatement(node) {
1331
1866
  return ';';
1332
1867
  }
1868
+ /**
1869
+ * @param {import("@babel/types").SpreadElement} node - The Babel AST node.
1870
+ * @returns {string} Stringification of the node.
1871
+ */
1333
1872
  SpreadElement(node) {
1334
1873
  const {
1335
1874
  argument
@@ -1337,7 +1876,13 @@ class Stringifier {
1337
1876
  const a = this.toSource(argument);
1338
1877
  return `...${a}`;
1339
1878
  }
1879
+ /**
1880
+ * @param {import("@babel/types").ObjectMethod} node - The Babel AST node.
1881
+ * @returns {string} Stringification of the node.
1882
+ */
1340
1883
  ObjectMethod(node) {
1884
+ // @todo PR in Babel why method/id is a key in node... (seems like a bug/oversight)
1885
+ // method === true just indicates kind === 'method'
1341
1886
  const {
1342
1887
  method,
1343
1888
  key,
@@ -1353,6 +1898,10 @@ class Stringifier {
1353
1898
  console.warn("ObjectMethod> id !== null is unhandled", node);
1354
1899
  debugger;
1355
1900
  }
1901
+ // if (method) {
1902
+ // console.warn("ObjectMethod> node was a method", node);
1903
+ // debugger;
1904
+ // }
1356
1905
  let out = this.spaces;
1357
1906
  if (generator) {
1358
1907
  out += '*';
@@ -1379,6 +1928,11 @@ class Stringifier {
1379
1928
  out += this.toSource(body);
1380
1929
  return out;
1381
1930
  }
1931
+ /**
1932
+ * E.g. function testComma({x,y,z,}) {}
1933
+ * @param {import("@babel/types").ObjectPattern} node - The Babel AST node.
1934
+ * @returns {string} Stringification of the node.
1935
+ */
1382
1936
  ObjectPattern(node) {
1383
1937
  const {
1384
1938
  properties,
@@ -1399,6 +1953,16 @@ class Stringifier {
1399
1953
  out += '}';
1400
1954
  return out;
1401
1955
  }
1956
+ /**
1957
+ * > x?.y
1958
+ * > x?.y.z
1959
+ * > x[y?.z]
1960
+ * > x?.[y?.z]
1961
+ * > b?.file?.variants[variant]
1962
+ *
1963
+ * @param {import("@babel/types").OptionalMemberExpression} node - The Babel AST node.
1964
+ * @returns {string} Stringification of the node.
1965
+ */
1402
1966
  OptionalMemberExpression(node) {
1403
1967
  const {
1404
1968
  object,
@@ -1408,6 +1972,7 @@ class Stringifier {
1408
1972
  } = node;
1409
1973
  const a = this.toSource(object);
1410
1974
  const b = this.toSource(property);
1975
+ // Basically four cases: computed=true/false optional=true/false, 00, 01, 10, 11
1411
1976
  if (!computed && !optional) {
1412
1977
  return `${a}.${b}`;
1413
1978
  } else if (!computed && optional) {
@@ -1415,8 +1980,17 @@ class Stringifier {
1415
1980
  } else if (computed && !optional) {
1416
1981
  return `${a}[${b}]`;
1417
1982
  }
1983
+ // Can only be case 4 now (computed && optional):
1418
1984
  return `${a}?.[${b}]`;
1419
1985
  }
1986
+ /**
1987
+ * > version?.indexOf('$');
1988
+ * > version?.indexOf?.('$');
1989
+ * > this.passEncoder?.pushDebugGroup(name);
1990
+ *
1991
+ * @param {import("@babel/types").OptionalCallExpression} node - The Babel AST node.
1992
+ * @returns {string} Stringification of the node.
1993
+ */
1420
1994
  OptionalCallExpression(node) {
1421
1995
  const {
1422
1996
  callee,
@@ -1430,6 +2004,10 @@ class Stringifier {
1430
2004
  }
1431
2005
  return `${a}(${b})`;
1432
2006
  }
2007
+ /**
2008
+ * @param {import("@babel/types").DoWhileStatement} node - The Babel AST node.
2009
+ * @returns {string} Stringification of the node.
2010
+ */
1433
2011
  DoWhileStatement(node) {
1434
2012
  const {
1435
2013
  body,
@@ -1445,6 +2023,10 @@ class Stringifier {
1445
2023
  out += ` while (${this.toSource(test)});`;
1446
2024
  return out;
1447
2025
  }
2026
+ /**
2027
+ * @param {import("@babel/types").TryStatement} node - The Babel AST node.
2028
+ * @returns {string} Stringification of the node.
2029
+ */
1448
2030
  TryStatement(node) {
1449
2031
  const {
1450
2032
  block,
@@ -1462,6 +2044,10 @@ class Stringifier {
1462
2044
  }
1463
2045
  return out;
1464
2046
  }
2047
+ /**
2048
+ * @param {import("@babel/types").CatchClause} node - The Babel AST node.
2049
+ * @returns {string} Stringification of the node.
2050
+ */
1465
2051
  CatchClause(node) {
1466
2052
  const {
1467
2053
  param,
@@ -1472,25 +2058,45 @@ class Stringifier {
1472
2058
  const p = this.toSource(param);
1473
2059
  return ` catch (${p}) ${b}`;
1474
2060
  }
2061
+ // Example: try { 1n / 0 } catch {}
1475
2062
  return ` catch ${b}`;
1476
2063
  }
2064
+ /**
2065
+ * @param {import("@babel/types").RestElement} node - The Babel AST node.
2066
+ * @returns {string} Stringification of the node.
2067
+ */
1477
2068
  RestElement(node) {
1478
2069
  const {
1479
2070
  argument
1480
2071
  } = node;
1481
2072
  return `...${this.toSource(argument)}`;
1482
2073
  }
2074
+ /**
2075
+ * @param {import("@babel/types").DebuggerStatement} node - The Babel AST node.
2076
+ * @returns {string} Stringification of the node.
2077
+ */
1483
2078
  DebuggerStatement(node) {
1484
2079
  return `${this.spaces}debugger;`;
1485
2080
  }
2081
+ /**
2082
+ * @param {import("@babel/types").Import} node - The Babel AST node.
2083
+ * @returns {string} Stringification of the node.
2084
+ */
1486
2085
  Import(node) {
1487
2086
  return 'import';
1488
2087
  }
2088
+ /**
2089
+ * @param {import("@babel/types").ImportDeclaration} node - The Babel AST node.
2090
+ * @returns {string} Stringification of the node.
2091
+ */
1489
2092
  ImportDeclaration(node) {
1490
2093
  const {
1491
2094
  specifiers,
1492
2095
  source
1493
2096
  } = node;
2097
+ // ImportNamespaceSpecifier: import * as a from "b";
2098
+ // ImportDefaultSpecifier : import a from "b";
2099
+ // ImportSpecifier : import { a } from "b";
1494
2100
  const a = this.mapToSource(specifiers).join(', ');
1495
2101
  const b = this.toSource(source);
1496
2102
  if (specifiers.length === 0) {
@@ -1501,6 +2107,10 @@ class Stringifier {
1501
2107
  }
1502
2108
  return `import {${a}} from ${b};`;
1503
2109
  }
2110
+ /**
2111
+ * @param {import("@babel/types").ImportSpecifier} node - The Babel AST node.
2112
+ * @returns {string} Stringification of the node.
2113
+ */
1504
2114
  ImportSpecifier(node) {
1505
2115
  const {
1506
2116
  imported,
@@ -1513,35 +2123,59 @@ class Stringifier {
1513
2123
  }
1514
2124
  return a;
1515
2125
  }
2126
+ /**
2127
+ * @param {import("@babel/types").ImportDefaultSpecifier} node - The Babel AST node.
2128
+ * @returns {string} Stringification of the node.
2129
+ */
1516
2130
  ImportDefaultSpecifier(node) {
1517
2131
  const {
1518
2132
  local
1519
2133
  } = node;
1520
2134
  return this.toSource(local);
1521
2135
  }
2136
+ /**
2137
+ * @param {import("@babel/types").ImportNamespaceSpecifier} node - The Babel AST node.
2138
+ * @returns {string} Stringification of the node.
2139
+ */
1522
2140
  ImportNamespaceSpecifier(node) {
1523
2141
  const {
1524
2142
  local
1525
2143
  } = node;
1526
2144
  return `* as ${this.toSource(local)}`;
1527
2145
  }
2146
+ /**
2147
+ * @param {import("@babel/types").File} node - The Babel AST node.
2148
+ * @returns {string} Stringification of the node.
2149
+ */
1528
2150
  File(node) {
1529
2151
  return this.toSource(node.program) + '\n';
1530
2152
  }
2153
+ /**
2154
+ * @param {import("@babel/types").Program} node - The Babel AST node.
2155
+ * @returns {string} Stringification of the node.
2156
+ */
1531
2157
  Program(node) {
1532
2158
  const {
1533
- body,
2159
+ /*sourceType, interpreter,*/body,
1534
2160
  directives
1535
2161
  } = node;
1536
2162
  let out = '';
2163
+ // @todo I would like to keep comments above and below,
2164
+ // but below one is currently dropped (does't matter for AST)
2165
+ // See: test/typechecking/directive.mjs
1537
2166
  out += this.mapToSource(directives);
1538
2167
  out += this.mapToSource(body).join('\n');
1539
2168
  return out;
1540
2169
  }
2170
+ /**
2171
+ * @param {import("@babel/types").StringLiteral} node - The Babel AST node.
2172
+ * @returns {string} Stringification of the node.
2173
+ */
1541
2174
  StringLiteral(node) {
1542
2175
  const {
1543
2176
  extra
1544
2177
  } = node;
2178
+ // Never experienced this so far, but types are types...
1545
2179
  if (!extra) {
1546
2180
  debugger;
1547
2181
  return '';
@@ -1552,9 +2186,17 @@ class Stringifier {
1552
2186
  }
1553
2187
  return extra.raw;
1554
2188
  }
2189
+ /**
2190
+ * @param {import("@babel/types").NumericLiteral} node - The Babel AST node.
2191
+ * @returns {string} Stringification of the node.
2192
+ */
1555
2193
  NumericLiteral(node) {
1556
2194
  return node.extra.raw;
1557
2195
  }
2196
+ /**
2197
+ * @param {import("@babel/types").AssignmentPattern} node - The Babel AST node.
2198
+ * @returns {string} Stringification of the node.
2199
+ */
1558
2200
  AssignmentPattern(node) {
1559
2201
  const {
1560
2202
  left,
@@ -1562,9 +2204,17 @@ class Stringifier {
1562
2204
  } = node;
1563
2205
  return `${this.toSource(left)} = ${this.toSource(right)}`;
1564
2206
  }
2207
+ /**
2208
+ * @param {import("@babel/types").NullLiteral} node - The Babel AST node.
2209
+ * @returns {string} Stringification of the node.
2210
+ */
1565
2211
  NullLiteral(node) {
1566
2212
  return 'null';
1567
2213
  }
2214
+ /**
2215
+ * @param {import("@babel/types").TaggedTemplateExpression} node - The Babel AST node.
2216
+ * @returns {string} Stringification of the node.
2217
+ */
1568
2218
  TaggedTemplateExpression(node) {
1569
2219
  const {
1570
2220
  tag,
@@ -1574,6 +2224,10 @@ class Stringifier {
1574
2224
  const q = this.toSource(quasi);
1575
2225
  return t + q;
1576
2226
  }
2227
+ /**
2228
+ * @param {import("@babel/types").YieldExpression} node - The Babel AST node.
2229
+ * @returns {string} Stringification of the node.
2230
+ */
1577
2231
  YieldExpression(node) {
1578
2232
  const {
1579
2233
  delegate,
@@ -1588,15 +2242,33 @@ class Stringifier {
1588
2242
  }
1589
2243
  }
1590
2244
 
2245
+ /** @typedef {import('@babel/types').Node } Node */
2246
+ /** @typedef {import("@babel/types").ClassMethod } ClassMethod */
2247
+ /** @typedef {import("@babel/types").ClassPrivateMethod} ClassPrivateMethod */
2248
+ /** @typedef {import('./stat.mjs').Stat } Stat */
2249
+ /**
2250
+ * @typedef {object} Options
2251
+ * @property {boolean} [forceCurly] - Determines whether curly braces are enforced in Stringifier.
2252
+ * @property {boolean} [validateDivision] - Indicates whether division operations should be validated.
2253
+ * @property {Function} [expandType] - A function that expands shorthand types into full descriptions.
2254
+ * @property {string} [filename] - The name of a file to which the instance pertains.
2255
+ * @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
2256
+ * @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
2257
+ */
1591
2258
  class Asserter extends Stringifier {
2259
+ /**
2260
+ * @param {Options} [options] - The options.
2261
+ */
1592
2262
  constructor({
1593
2263
  forceCurly = true,
1594
2264
  validateDivision = true,
1595
2265
  expandType = expandTypeDepFree,
1596
2266
  filename,
1597
- addHeader = true
2267
+ addHeader = true,
2268
+ ignoreLocations = []
1598
2269
  } = {}) {
1599
2270
  super();
2271
+ /** @type {Record<string, Stat>} */
1600
2272
  this.stats = {
1601
2273
  'ArrowFunctionExpression': {
1602
2274
  checked: 0,
@@ -1643,19 +2315,29 @@ class Asserter extends Stringifier {
1643
2315
  unchecked: 0
1644
2316
  }
1645
2317
  };
2318
+ /** @type {Record<string, object>} */
1646
2319
  this.typedefs = {};
1647
2320
  this.forceCurly = forceCurly;
1648
2321
  this.validateDivision = validateDivision;
2322
+ // @todo collect every type + manually validate as test set
2323
+ // + implement expandType using Babel Flow type parser aswell
1649
2324
  this.expandType = expandType;
1650
2325
  this.filename = filename;
1651
2326
  this.addHeader = addHeader;
1652
- }
2327
+ this.ignoreLocations = ignoreLocations;
2328
+ }
2329
+ /**
2330
+ * We expand type-asserted ArrowFunctionExpressions in order to add type assertions.
2331
+ * @override
2332
+ * @param {import("@babel/types").ArrowFunctionExpression} node - The Babel AST node.
2333
+ * @returns {string} Stringification of the node.
2334
+ */
1653
2335
  ArrowFunctionExpression(node) {
1654
2336
  const {
1655
2337
  async,
1656
2338
  body,
1657
2339
  generator,
1658
- params
2340
+ params /*, extra*/
1659
2341
  } = node;
1660
2342
  let out = '';
1661
2343
  if (async) {
@@ -1676,6 +2358,11 @@ class Asserter extends Stringifier {
1676
2358
  }
1677
2359
  return out;
1678
2360
  }
2361
+ /**
2362
+ * @override
2363
+ * @param {import("@babel/types").ClassDeclaration} node - The Babel AST node.
2364
+ * @returns {string} Stringification of the node.
2365
+ */
1679
2366
  ClassDeclaration(node) {
1680
2367
  const {
1681
2368
  id
@@ -1685,6 +2372,14 @@ class Asserter extends Stringifier {
1685
2372
  out += `${this.spaces}registerClass(${id_});`;
1686
2373
  return out;
1687
2374
  }
2375
+ /**
2376
+ * Finds the closest ancestor of the given node that matches the specified type.
2377
+ *
2378
+ * @param {Node} node - The starting node to search from.
2379
+ * @param {T} type - Type name of the node to search for.
2380
+ * @template {Node['type']} T
2381
+ * @returns {Extract<Node, {type: T}>|undefined} The first ancestor node of the specified type, or undefined if none is found.
2382
+ */
1688
2383
  findParentOfType(node, type) {
1689
2384
  const currentIndex = this.parents.findLastIndex(_ => _ === node);
1690
2385
  return this.parents.findLast((_, i) => {
@@ -1694,6 +2389,13 @@ class Asserter extends Stringifier {
1694
2389
  return _.type === type;
1695
2390
  });
1696
2391
  }
2392
+ /**
2393
+ * Alternatively "import * as rti from ..." would also prevent "Unused external imports" warning...
2394
+ * or keeping log of every single call during RTI parsing.
2395
+ * @todo
2396
+ * Once we went over every node, we can see if we really require registerTypef, registerClass etc.
2397
+ * @returns {string} The import declaration header for importing RTI.
2398
+ */
1697
2399
  getHeader() {
1698
2400
  if (!this.addHeader) {
1699
2401
  return '';
@@ -1703,9 +2405,19 @@ class Asserter extends Stringifier {
1703
2405
  header += ", validateDivision";
1704
2406
  }
1705
2407
  header += ", registerTypedef, registerClass} from '@runtime-type-inspector/runtime';\n";
2408
+ // Prevent tree-shaking in UMD build so we can always "add a breakpoint here".
1706
2409
  header += "export * from '@runtime-type-inspector/runtime';\n";
1707
2410
  return header;
1708
2411
  }
2412
+ /**
2413
+ * Retrieves the node associated with the leading comments for an arrow function expression.
2414
+ *
2415
+ * This method travels up the syntax tree from the given node to find a parent node with leading comments.
2416
+ * This search is bounded by function boundaries or a 'CallExpression' node, as per the logic defined within the loop.
2417
+ * @param {Node} node - The node representing the arrow function expression for which to find the leading comments node.
2418
+ * @returns {Node|undefined} The node that contains the leading comments, or `undefined`
2419
+ * if none is found before reaching a different function or 'CallExpression'.
2420
+ */
1709
2421
  getLeadingCommentsNodeForArrowFunctionExpression(node) {
1710
2422
  const {
1711
2423
  parents
@@ -1715,9 +2427,16 @@ class Asserter extends Stringifier {
1715
2427
  if (parent.leadingComments) {
1716
2428
  return parent;
1717
2429
  }
2430
+ // Skip now, if we find another function first,
2431
+ // there is no JSDoc for our function anymore.
2432
+ // Not interested in our start node if it didn't
2433
+ // contain leadingComments.
1718
2434
  i--;
1719
2435
  while (i >= 0) {
1720
2436
  parent = parents[i];
2437
+ //if (parent.type === 'CallExpression') {
2438
+ // break;
2439
+ //}
1721
2440
  if (nodeIsFunction(parent)) {
1722
2441
  break;
1723
2442
  }
@@ -1727,6 +2446,10 @@ class Asserter extends Stringifier {
1727
2446
  i--;
1728
2447
  }
1729
2448
  }
2449
+ /**
2450
+ * Emits a warning message to the console, optionally prefixed with the instance's filename.
2451
+ * @param {...any} args - A list of arguments to be passed to the console.warn function.
2452
+ */
1730
2453
  warn(...args) {
1731
2454
  if (this.filename) {
1732
2455
  console.warn("[WARN]", this.filename);
@@ -1742,9 +2465,16 @@ class Asserter extends Stringifier {
1742
2465
  if (parent.leadingComments) {
1743
2466
  return parent;
1744
2467
  }
2468
+ // Skip now, if we find another function first,
2469
+ // there is no JSDoc for our function anymore.
2470
+ // Not interested in our start node if it didn't
2471
+ // contain leadingComments.
1745
2472
  i--;
1746
2473
  while (i >= 0) {
1747
2474
  parent = parents[i];
2475
+ //if (parent.type === 'CallExpression') {
2476
+ // break;
2477
+ //}
1748
2478
  if (nodeIsFunction(parent)) {
1749
2479
  break;
1750
2480
  }
@@ -1753,7 +2483,26 @@ class Asserter extends Stringifier {
1753
2483
  }
1754
2484
  i--;
1755
2485
  }
1756
- }
2486
+ /** @todo convert all files in test/typechecking/*.mjs into full unit tests */
2487
+ // Old way:
2488
+ // if (node.leadingComments) {
2489
+ // return node.leadingComments;
2490
+ // }
2491
+ // node = this.findParentOfType(node, 'ExpressionStatement');
2492
+ // if (!node) {
2493
+ // /**
2494
+ // * @todo Need more refactoring, see missing type-assertions in test/typechecking/good-old-es5.mjs
2495
+ // */
2496
+ // node = this.parents.findLast(_ => _.type === 'VariableDeclaration');
2497
+ // if (!node) {
2498
+ // return;
2499
+ // }
2500
+ // }
2501
+ }
2502
+ /**
2503
+ * @param {Node} node - The Babel AST node.
2504
+ * @returns {undefined | {}} The return value of `parseJSDoc`.
2505
+ */
1757
2506
  getJSDoc(node) {
1758
2507
  if (node.type === 'BlockStatement') {
1759
2508
  node = this.parent;
@@ -1761,10 +2510,12 @@ class Asserter extends Stringifier {
1761
2510
  let {
1762
2511
  leadingComments
1763
2512
  } = node;
2513
+ // Receive the leadingComments from the ExpressionStatement, not the FunctionExpression itself.
1764
2514
  if (node.type === 'FunctionExpression') {
1765
2515
  const tmp = this.getLeadingCommentsNodeForFunctionExpression(node);
1766
2516
  leadingComments = tmp == null ? void 0 : tmp.leadingComments;
1767
2517
  }
2518
+ // Receive the leadingComments from ExportNamedDeclaration, if FunctionDeclaration has none
1768
2519
  if (!leadingComments) {
1769
2520
  if (node.type === 'FunctionDeclaration') {
1770
2521
  const exportNamedDeclaration = this.findParentOfType(node, 'ExportNamedDeclaration');
@@ -1797,6 +2548,15 @@ class Asserter extends Stringifier {
1797
2548
  }
1798
2549
  }
1799
2550
  }
2551
+ /**
2552
+ * Retrieves the name of a parameter from a Babel AST node.
2553
+ *
2554
+ * This function expects a node representing a function parameter and attempts to extract
2555
+ * the parameter's name directly or from an AssignmentPattern.
2556
+ *
2557
+ * @param {Node} param - The AST node representing the function parameter from which to extract the name.
2558
+ * @returns {string} The name of the parameter as a string, or the parameter's source code if the extraction fails.
2559
+ */
1800
2560
  getNameOfParam(param) {
1801
2561
  if (param.type === 'Identifier') {
1802
2562
  return param.name;
@@ -1815,19 +2575,29 @@ class Asserter extends Stringifier {
1815
2575
  statsPrint() {
1816
2576
  console.table(this.stats);
1817
2577
  }
2578
+ /**
2579
+ * Retrieves statistical information for a given Babel AST node of this instance.
2580
+ *
2581
+ * @param {Node} node - The Babel AST node for which the statistical data is retrieved.
2582
+ * @returns {Stat} An object containing the statistical data for the specified node. If the
2583
+ * node type is unhandled, defaults to returning a dummy object with 'checked' and 'unchecked'
2584
+ * properties both set to 0.
2585
+ */
1818
2586
  getStatsForNode(node) {
1819
2587
  const {
1820
2588
  stats
1821
2589
  } = this;
1822
2590
  const type = nodeIsFunction(node) ? node.type : this.parentType;
1823
2591
  if (type === 'ClassMethod') {
1824
- const parent = this.parent;
2592
+ const parent = /** @type {ClassMethod} */
2593
+ this.parent;
1825
2594
  const {
1826
2595
  kind
1827
2596
  } = parent;
1828
2597
  return stats[`ClassMethod#${kind}`];
1829
2598
  } else if (type === 'ClassPrivateMethod') {
1830
- const parent = this.parent;
2599
+ const parent = /** @type {ClassPrivateMethod} */
2600
+ this.parent;
1831
2601
  const {
1832
2602
  kind
1833
2603
  } = parent;
@@ -1843,6 +2613,17 @@ class Asserter extends Stringifier {
1843
2613
  }
1844
2614
  return stat;
1845
2615
  }
2616
+ /**
2617
+ * Checks if a provided Babel AST node has a parameter with the given name.
2618
+ *
2619
+ * This function will look at the node's parameters if available and determine whether
2620
+ * one of them matches the provided name. Supports various parameter types such as Identifiers
2621
+ * and AssignmentPatterns.
2622
+ *
2623
+ * @param {Node} node - The Babel AST node to inspect. If it's a BlockStatement, the parent node is used instead.
2624
+ * @param {string} name - The name of the parameter to look for within the node's parameters.
2625
+ * @returns {boolean} True if the node has a parameter with the given name; false otherwise.
2626
+ */
1846
2627
  nodeHasParamName(node, name) {
1847
2628
  if (node.type === 'BlockStatement') {
1848
2629
  node = this.parent;
@@ -1878,6 +2659,18 @@ class Asserter extends Stringifier {
1878
2659
  return false;
1879
2660
  });
1880
2661
  }
2662
+ /**
2663
+ * Generates a string containing type checks for a given Babel AST node based on associated JSDoc information.
2664
+ *
2665
+ * This function analyzes the node and its JSDoc annotations to construct runtime type
2666
+ * check expressions. It handles various parameter patterns and outputs code that performs
2667
+ * actual type assertions. If a node does not correspond to any known or supported pattern,
2668
+ * it returns an empty string.
2669
+ *
2670
+ * @override
2671
+ * @param {Node} node - The Babel AST node for which to generate type checks.
2672
+ * @returns {string} A string of code with type check assertions, based on the JSDoc comments associated with the given node.
2673
+ */
1881
2674
  generateTypeChecks(node) {
1882
2675
  const {
1883
2676
  parent
@@ -1886,6 +2679,7 @@ class Asserter extends Stringifier {
1886
2679
  return '';
1887
2680
  }
1888
2681
  const jsdoc = this.getJSDoc(node);
2682
+ // return '// ' + JSON.stringify(jsdoc) + '\n';
1889
2683
  const stat = this.getStatsForNode(node);
1890
2684
  if (!jsdoc) {
1891
2685
  stat.unchecked++;
@@ -1897,6 +2691,12 @@ class Asserter extends Stringifier {
1897
2691
  } = this;
1898
2692
  let out = '';
1899
2693
  let first = true;
2694
+ const loc = this.getName(node);
2695
+ if (this.ignoreLocations.includes(loc)) {
2696
+ return '// IGNORE RTI TYPE VALIDATIONS, KNOWN ISSUES\n';
2697
+ }
2698
+ //out += `${spaces}/*${spaces} node.type=${node.type}\n${spaces}
2699
+ // ${JSON.stringify(jsdoc)}\n${parent}\n${spaces}*/\n`;
1900
2700
  for (let name in jsdoc) {
1901
2701
  const type = jsdoc[name];
1902
2702
  const hasParam = this.nodeHasParamName(node, name);
@@ -1911,11 +2711,24 @@ class Asserter extends Stringifier {
1911
2711
  const isObjectPattern = param.type === 'ObjectPattern';
1912
2712
  const isArrayPattern = param.type === 'ArrayPattern';
1913
2713
  const isSupportedPattern = isObjectPattern || isArrayPattern;
2714
+ // There are four kinds of patterns:
2715
+ // ObjectPattern:
2716
+ // function test({x = 123}) {return x;} test({x: 456});
2717
+ // ArrayPattern:
2718
+ // function test([x = 123]) {return x;}; test([456]);
2719
+ // AssignmentPattern made up of ObjectPattern:
2720
+ // function test({x = 123} = {}) {return x;} test();
2721
+ // AssignmentPattern made up of ArrayPattern:
2722
+ // function test([x = 123] = []) {return x;} test();
1914
2723
  if (isSupportedPattern) {
2724
+ // The name doesn't matter any longer, because any pattern inherently
2725
+ // drops the identifier from the AST. But we can access it
2726
+ // via arguments[paramIndex] anyway.
1915
2727
  name = `arguments[${paramIndex}]`;
1916
2728
  } else if (param.type === 'AssignmentPattern') {
1917
2729
  const _loc = this.getName(node);
1918
2730
  if (param.left.type === 'ArrayPattern' && type.type === 'array') {
2731
+ // Add a type assertion for each element of the ArrayPattern
1919
2732
  for (const element of param.left.elements) {
1920
2733
  if (element.type !== 'Identifier') {
1921
2734
  this.warn('Only Identifier case handled right now');
@@ -1927,6 +2740,7 @@ class Asserter extends Stringifier {
1927
2740
  }
1928
2741
  continue;
1929
2742
  } else if (param.left.type === 'ObjectPattern' && type.type === 'object') {
2743
+ // Add a type assertion for each property of the ObjectPattern
1930
2744
  for (const property of param.left.properties) {
1931
2745
  if (property.key.type !== 'Identifier') {
1932
2746
  this.warn('ObjectPattern> Only Identifier case handled right now');
@@ -1965,27 +2779,39 @@ class Asserter extends Stringifier {
1965
2779
  }
1966
2780
  t = '"' + this.toSource(classDecl.id) + '"';
1967
2781
  }
1968
- const loc = this.getName(node);
2782
+ const _loc3 = this.getName(node);
1969
2783
  let prevCheck = '';
1970
- if (loc === 'ContactPoint#constructor' || loc === 'ContactResult#constructor' || loc === 'SingleContactResult#constructor') {
2784
+ // JSDoc doesn't support multiple function signatures yet, but this is
2785
+ // exactly what we would need to deal with ObjectPool'ing
2786
+ if (_loc3 === 'ContactPoint#constructor' || _loc3 === 'ContactResult#constructor' || _loc3 === 'SingleContactResult#constructor') {
1971
2787
  prevCheck = 'arguments.length !== 0 && ';
1972
2788
  }
1973
2789
  if (first) {
1974
2790
  out += '\n';
1975
2791
  first = false;
1976
2792
  }
1977
- out += `${spaces}if (${prevCheck}!assertType(${name}, ${t}, '${loc}', '${name}')) {\n`;
2793
+ out += `${spaces}if (${prevCheck}!assertType(${name}, ${t}, '${_loc3}', '${name}')) {\n`;
1978
2794
  out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
1979
2795
  }
1980
2796
  return out;
1981
2797
  }
2798
+ /**
2799
+ * @param {Node} node - The Babel AST node.
2800
+ * @returns {string} Best possible human-readable name of given node.
2801
+ */
1982
2802
  getNameForFunctionExpression(node) {
1983
2803
  const objectProperty = this.findParentOfType(node, 'ObjectProperty');
1984
2804
  if (objectProperty) {
2805
+ // See good-old-es5.mjs example for a test case
2806
+ // TODO: Make an even better name based on Object.assign(ScopeSpace.prototype
2807
+ // Ideally we would figure out the name: ScopeSpace#resolve
2808
+ // Currently we only find "resolve" (still better than 'unnamed function expression'...)
1985
2809
  return this.toSource(objectProperty.key);
1986
2810
  }
1987
2811
  const expressionStatement = this.findParentOfType(node, 'ExpressionStatement');
1988
2812
  if (expressionStatement) {
2813
+ // There are many kinds of expressions
2814
+ // type Expression = ArrayExpression | AssignmentExpression | BinaryExpression | CallExpression | ...
1989
2815
  const {
1990
2816
  left
1991
2817
  } = expressionStatement.expression;
@@ -1996,6 +2822,10 @@ class Asserter extends Stringifier {
1996
2822
  }
1997
2823
  return 'unnamed function expression';
1998
2824
  }
2825
+ /**
2826
+ * @param {Node} node - The Babel AST node.
2827
+ * @returns {string} Stringification of the node.
2828
+ */
1999
2829
  getName(node) {
2000
2830
  const toSource = this.toSource.bind(this);
2001
2831
  if (node.type === 'BlockStatement') {
@@ -2036,6 +2866,11 @@ class Asserter extends Stringifier {
2036
2866
  return '/*MISSING*/';
2037
2867
  }
2038
2868
  }
2869
+ /**
2870
+ * @override
2871
+ * @param {import("@babel/types").BinaryExpression} node - The Babel AST node.
2872
+ * @returns {string} Stringification of the node.
2873
+ */
2039
2874
  BinaryExpression(node) {
2040
2875
  if (!this.validateDivision) {
2041
2876
  return super.BinaryExpression(node);
@@ -2052,9 +2887,15 @@ class Asserter extends Stringifier {
2052
2887
  }
2053
2888
  return `${left_} ${operator} ${right_}`;
2054
2889
  }
2890
+ /**
2891
+ * @override
2892
+ * @param {import("@babel/types").File} node - The Babel AST node.
2893
+ * @returns {string} Stringification of the node.
2894
+ */
2055
2895
  File(node) {
2896
+ // @todo figure out why errors is in Babel node and not in @babel/types...
2056
2897
  const {
2057
- program,
2898
+ /*errors,*/program,
2058
2899
  comments
2059
2900
  } = node;
2060
2901
  if (comments) {
@@ -2063,6 +2904,7 @@ class Asserter extends Stringifier {
2063
2904
  parseJSDocTypedef(this.typedefs, warn, comment, this.expandType);
2064
2905
  }
2065
2906
  }
2907
+ //console.log("this.typedefs", this.typedefs);
2066
2908
  let out = '';
2067
2909
  for (const name in this.typedefs) {
2068
2910
  const typedef = this.typedefs[name];
@@ -2075,7 +2917,24 @@ class Asserter extends Stringifier {
2075
2917
  }
2076
2918
  }
2077
2919
 
2920
+ /**
2921
+ * Simple facade which does all the processing. Processes the input
2922
+ * source string, adding runtime type checks based on JSDoc comments.
2923
+ *
2924
+ * This function takes JavaScript source code as input, parses it to an AST, traverses the
2925
+ * AST to find type annotations in JSDoc comments, and generates appropriate runtime type
2926
+ * assertions. These are then inserted into the source, producing a new version of the code
2927
+ * that includes runtime type checking based on the original JSDoc annotations.
2928
+ *
2929
+ * @param {string} src - The input source code containing JSDoc comments to be processed
2930
+ * for type checks.
2931
+ * @param {import('./Asserter.mjs').Options} [options] - Configuration options that dictate
2932
+ * how the processing is performed.
2933
+ * @returns {string} The transformed source code with inserted runtime type checks, or the
2934
+ * original source code commented with an error if processing fails.
2935
+ */
2078
2936
  function addTypeChecks(src, options) {
2937
+ console.log("GOT OPTIONS", options);
2079
2938
  try {
2080
2939
  const asserter = new Asserter(options);
2081
2940
  const ast = parse(src, {
@@ -2090,28 +2949,45 @@ function addTypeChecks(src, options) {
2090
2949
  }
2091
2950
  }
2092
2951
 
2952
+ /**
2953
+ * @param {object} ast - The Babel AST.
2954
+ * @returns {string} String representation in JSON format for debugging/inspecting the AST.
2955
+ */
2093
2956
  function ast2json(ast) {
2094
2957
  return JSON.stringify(ast, function (name, val) {
2095
2958
  if (name === "loc" || name === "start" || name === "end") {
2096
- return;
2959
+ return; // remove
2097
2960
  }
2098
- return val;
2961
+
2962
+ return val; // keep
2099
2963
  }, 2);
2100
2964
  }
2101
2965
 
2102
2966
  const drop = ['loc', 'start', 'end', 'leadingComments', 'trailingComments', 'innerComments', 'innerComments', 'comments'];
2967
+ /**
2968
+ * @example
2969
+ * setRight(ast2jsonForComparison(parseSync("/** *"))); // Close comment with / after last *
2970
+ * @param {object} ast - The Babel AST.
2971
+ * @returns {string} String representation in JSON format for debugging/inspecting the AST.
2972
+ */
2103
2973
  function ast2jsonForComparison(ast) {
2104
2974
  return JSON.stringify(ast, function (name, val) {
2105
2975
  if (name === 'trailingComma' || name === 'parenStart') {
2106
2976
  return 'offset removed for better comparison';
2107
2977
  }
2108
2978
  if (drop.includes(name)) {
2109
- return undefined;
2979
+ return undefined; // remove
2110
2980
  }
2111
- return val;
2981
+
2982
+ return val; // keep
2112
2983
  }, 2);
2113
2984
  }
2114
2985
 
2986
+ /**
2987
+ * A roundtrip between code -> AST -> code to validate Stringifier.
2988
+ * @param {string} code - The code.
2989
+ * @returns {string | undefined} The new and once parsed and stringified code.
2990
+ */
2115
2991
  function code2ast2code(code) {
2116
2992
  const stringifier = new Stringifier();
2117
2993
  const ast = parse(code, {
@@ -2124,6 +3000,11 @@ function code2ast2code(code) {
2124
3000
  return out;
2125
3001
  }
2126
3002
 
3003
+ /**
3004
+ * @param {string} left - Left source code.
3005
+ * @param {string} right - Right source code.
3006
+ * @returns {boolean} Whether source codes are identical on the AST level.
3007
+ */
2127
3008
  function compareAST(left, right) {
2128
3009
  const l = parse(left, {
2129
3010
  sourceType: 'module'
@@ -2137,19 +3018,70 @@ function compareAST(left, right) {
2137
3018
  return test;
2138
3019
  }
2139
3020
 
3021
+ /**
3022
+ * Transforms a type string into a structured type representation.
3023
+ *
3024
+ * This function parses a given type string and converts it into a TypeScript
3025
+ * Abstract Syntax Tree (AST), then uses that AST to return a structured type
3026
+ * representation that can be further utilized or interpreted.
3027
+ *
3028
+ * @todo Better handling of weird case: Array<>
3029
+ * @example
3030
+ * const {expandType} = await import("./src-transpiler/expandType.mjs");
3031
+ * expandType('[string, Array|AnyTypedArray, number[]]|[ONNXTensor]');
3032
+ * expandType('(123) '); // Outputs: '123'
3033
+ * expandType(' ( ( 123 ) ) '); // Outputs: '123'
3034
+ * expandType('Array<number> '); // Outputs: {type: 'array', elementType: 'number'}
3035
+ * expandType('Array<(123) > '); // Outputs: {type: 'array', elementType: '123'}
3036
+ * expandType('Array<"abc" | 123> '); // Outputs: {type: 'array', elementType: {type: 'union', members: ['"abc"', '123']}}
3037
+ * expandType(' (string ) |(number ) '); // Outputs: {type: 'union', members: [ 'string', 'number']}
3038
+ * expandType(' "apples" | ( "bananas") '); // Outputs: {type: 'union', members: [ '"apples"', '"bananas"']}
3039
+ * expandType('123? '); // Outputs: {"type":"union","members":["123","null"]}
3040
+ * expandType('123|null '); // Outputs: {"type":"union","members":["123","null"]}
3041
+ * expandType('Map<string, any> '); // Outputs:
3042
+ * expandType('typeof Number '); // Outputs:
3043
+ * @param {string} type - The type string to be expanded into a structured representation.
3044
+ * @todo Share type with expandTypeBabelTS and expandTypeDepFree
3045
+ * @returns {string | {type: string, [key: string]: any} | undefined} The structured type
3046
+ * representation obtained from parsing and converting the provided type string.
3047
+ */
2140
3048
  function expandType(type) {
2141
3049
  const ast = parseType(type);
2142
3050
  return toSourceTS(ast);
2143
3051
  }
3052
+ /**
3053
+ * @todo I want to use for example: import('typescript').Node
3054
+ * But the TS types make no sense to me so far ... need to investigate more.
3055
+ * @typedef TypeScriptType
3056
+ * @property {object[]|undefined} typeArguments - The type arguments.
3057
+ * @property {import('typescript').Node} typeName - The type name.
3058
+ * @property {number} kind - The kind for `ts.SyntaxKind[kind]`.
3059
+ */
3060
+ /**
3061
+ * @param {string} str - The type string.
3062
+ * @returns {TypeScriptType} - The node containing all the information about the input type string.
3063
+ */
2144
3064
  function parseType(str) {
3065
+ // TS doesn't like ... notation in this context
2145
3066
  if (str.startsWith('...')) {
2146
- str = str.slice(3);
2147
- str += '[]';
3067
+ str = str.slice(3); // remove dots
3068
+ str += '[]'; // turn into array
2148
3069
  }
3070
+ // type tmp = (...string) => 123; to have a function context
2149
3071
  str = `type tmp = ${str};`;
2150
- const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, true);
3072
+ const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, true /*setParentNodes*/);
2151
3073
  return ast.statements[0].type;
2152
3074
  }
3075
+ /**
3076
+ * Converts a TypeScript AST node to a source string representation or to an intermediate object describing the type.
3077
+ *
3078
+ * This function handles various TypeScript AST node types and converts them into a string
3079
+ * or an object representing the type.
3080
+ *
3081
+ * @param {TypeScriptType} node - The TypeScript AST node to convert.
3082
+ * @returns {string | {type: string, [key: string]: any} | undefined} The source string or an object with type information based on the node,
3083
+ * or `undefined` if the node kind is not handled.
3084
+ */
2153
3085
  function toSourceTS(node) {
2154
3086
  const {
2155
3087
  typeArguments,
@@ -2193,23 +3125,31 @@ function toSourceTS(node) {
2193
3125
  IndexedAccessType,
2194
3126
  RestType,
2195
3127
  TypeQuery,
3128
+ // parseType('typeof Number')
2196
3129
  TypeOperator,
3130
+ // parseType('keyof typeof obj')
2197
3131
  KeyOfKeyword,
3132
+ // "operator" key in TypeOperator node
2198
3133
  ConstructorType,
3134
+ // parseType('new (...args: any[]) => any');
2199
3135
  NamedTupleMember
2200
3136
  } = ts.SyntaxKind;
3137
+ // console.log({typeArguments, typeName, kind_, node});
2201
3138
  switch (node.kind) {
2202
3139
  case BigIntKeyword:
2203
3140
  return {
2204
3141
  type: 'bigint'
2205
3142
  };
2206
3143
  case BigIntLiteral:
2207
- const literal = node.text.slice(0, -1);
3144
+ const literal = node.text.slice(0, -1); // Remove the "n"
2208
3145
  return {
2209
3146
  type: 'bigint',
2210
3147
  literal
2211
3148
  };
2212
3149
  case ConditionalType:
3150
+ // Keys on node:
3151
+ // ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
3152
+ // 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
2213
3153
  const checkType = toSourceTS(node.checkType);
2214
3154
  const extendsType = toSourceTS(node.extendsType);
2215
3155
  const trueType = toSourceTS(node.trueType);
@@ -2257,6 +3197,8 @@ function toSourceTS(node) {
2257
3197
  type: 'union',
2258
3198
  members: [t, 'null']
2259
3199
  };
3200
+ // todo work out more: const jsdoc = `(...a: ...number) => 123
3201
+ // TS even thinks it's two parameters... just go for array/[]
2260
3202
  case Parameter:
2261
3203
  const type = node.type ? toSourceTS(node.type) : 'any';
2262
3204
  const name = toSourceTS(node.name);
@@ -2389,6 +3331,7 @@ function toSourceTS(node) {
2389
3331
  return toSourceTS(node.literal);
2390
3332
  case AnyKeyword:
2391
3333
  case BooleanKeyword:
3334
+ // ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
2392
3335
  case JSDocAllType:
2393
3336
  case NullKeyword:
2394
3337
  case NumericLiteral:
@@ -2407,10 +3350,17 @@ function toSourceTS(node) {
2407
3350
  properties: {}
2408
3351
  };
2409
3352
  case ParenthesizedType:
3353
+ // fall-through for parentheses
2410
3354
  return toSourceTS(node.type);
2411
3355
  case LastTypeNode:
2412
3356
  return toSourceTS(node.qualifier);
2413
3357
  default:
3358
+ // const test = {};
3359
+ // Object.entries(ts.SyntaxKind).forEach(([name, id]) => {
3360
+ // test[id] = (test[id] || []);
3361
+ // test[id].push(name);
3362
+ // });
3363
+ // console.log(test);
2414
3364
  console.warn('toSourceTS> unhandled kind - make sure to understand you cannot reverse TS enums');
2415
3365
  console.warn('if they contain range aliases, for example:');
2416
3366
  console.warn('ts.SyntaxKind.NumericLiteral === ts.SyntaxKind.FirstLiteralToken');
@@ -2419,17 +3369,58 @@ function toSourceTS(node) {
2419
3369
  }
2420
3370
  }
2421
3371
 
3372
+ /**
3373
+ * @todo Better handling of weird case: Array<>
3374
+ * @example
3375
+ * const {expandTypeBabelTS} = await import("./src-transpiler/expandTypeBabelTS.mjs");
3376
+ * expandTypeBabelTS('[string, Array|AnyTypedArray, number[]]|[ONNXTensor]');
3377
+ * expandTypeBabelTS('(123) '); // Outputs: '123'
3378
+ * expandTypeBabelTS(' ( ( 123 ) ) '); // Outputs: '123'
3379
+ * expandTypeBabelTS('Array<number> '); // Outputs: {type: 'array', elementType: 'number'}
3380
+ * expandTypeBabelTS('Array<(123) > '); // Outputs: {type: 'array', elementType: '123'}
3381
+ * expandTypeBabelTS('Array<"abc" | 123> '); // Outputs: {type: 'array', elementType: {type: 'union', members: ['"abc"', '123']}}
3382
+ * expandTypeBabelTS(' (string ) |(number ) '); // Outputs: {type: 'union', members: [ 'string', 'number']}
3383
+ * expandTypeBabelTS(' "apples" | ( "bananas") '); // Outputs: {type: 'union', members: [ '"apples"', '"bananas"']}
3384
+ * expandTypeBabelTS('123? '); // Outputs: {type: 'union', members: ['123', 'null']}
3385
+ * expandTypeBabelTS('123|null '); // Outputs: {type: 'union', members: ['123', 'null']}
3386
+ * expandTypeBabelTS('Map<string, any> '); // Outputs: {type: 'map', key: 'string', val: 'any'}
3387
+ * expandTypeBabelTS("(a: number, b: number) => number")
3388
+ * @param {string} type - The input type.
3389
+ * @returns {string|object|undefined} - See `toSourceBabelTS`.
3390
+ */
2422
3391
  function expandTypeBabelTS(type) {
2423
3392
  const ast = parseTypeBabelTS(type);
2424
3393
  return toSourceBabelTS(ast);
2425
3394
  }
3395
+ /**
3396
+ * @param {string} str - The type string.
3397
+ * @returns {import('@babel/types').Node} - The node containing all the information about the input type string.
3398
+ */
2426
3399
  function parseTypeBabelTS(str) {
3400
+ // TS doesn't like ... notation in this context
3401
+ //if (str.startsWith('...')) {
3402
+ // str = str.slice(3); // remove dots
3403
+ // str += '[]'; // turn into array
3404
+ //}
3405
+ // type tmp = (...string) => 123; to have a function context
2427
3406
  str = `type tmp = ${str};`;
2428
3407
  const ast = parse(str, {
2429
3408
  plugins: ['typescript']
2430
3409
  });
2431
3410
  return ast.program.body[0].typeAnnotation;
2432
3411
  }
3412
+ /**
3413
+ * Converts a Babel AST node to its source string representation or structured type object.
3414
+ *
3415
+ * This function handles a variety of node types provided by Babel and converts them into a string
3416
+ * or an intermediate object representing the type, depending on the complexity of the type described by the node.
3417
+ *
3418
+ * @param {import('@babel/types').Node} node - The Babel AST node to convert.
3419
+ * @returns {string|object|undefined} - A string, object representing a structured type, or `undefined` for unhandled types.
3420
+ * Depending on the node, it may return a simple type string (e.g., `"string"` for `TSStringKeyword`),
3421
+ * a structured type object (e.g., a record type for `TSTypeReference` with type arguments),
3422
+ * or `undefined` if the encountered type is not handled. Unhandled types trigger a warning and enter a debugger statement.
3423
+ */
2433
3424
  function toSourceBabelTS(node) {
2434
3425
  switch (node.type) {
2435
3426
  case 'TSBigIntKeyword':
@@ -2442,10 +3433,34 @@ function toSourceBabelTS(node) {
2442
3433
  type: 'bigint',
2443
3434
  literal
2444
3435
  };
3436
+ // expandTypeBabelTS("(a: number, b: number) => number")
3437
+ /**
3438
+ * @todo the parameters are given as identifiers with "typeAnnotation"
3439
+ */
3440
+ // case 'TSFunctionType':
3441
+ // const parameters = node.parameters.map(toSourceBabelTS);
3442
+ // // I wish Babel AST would be like tsc AST here:
3443
+ // // parseType("(a: number, b: number) => number").parameters[0].kind === ts.SyntaxKind.Parameter
3444
+ // return {type: 'function', parameters};
3445
+ // Fix first: https://github.com/babel/babel/issues/16073
3446
+ //case 'JSDocNullableType':
3447
+ // const t = toSourceBabelTS(node.type);
3448
+ // return {type: 'union', members: [t, 'null']};
3449
+ // todo work out more: const jsdoc = `(...a: ...number) => 123
3450
+ // TS even thinks it's two parameters... just go for array/[]
3451
+ //case 'Parameter':
3452
+ // const type = node.type ? toSourceBabelTS(node.type) : 'any';
3453
+ // const name = toSourceBabelTS(node.name);
3454
+ // const ret = {type, name};
3455
+ // if (node.dotDotDotToken) {
3456
+ // return {type: 'array', elementType: ret};
3457
+ // }
3458
+ // return ret;
2445
3459
  case 'TSTypeReference':
2446
3460
  {
2447
3461
  const name = toSourceBabelTS(node.typeName);
2448
3462
  if (!node.typeParameters) {
3463
+ // console.log(`node.typeName.name=${node.typeName.name} name=${name}`, node);
2449
3464
  return node.typeName.name;
2450
3465
  }
2451
3466
  console.assert(node.typeParameters.type === 'TSTypeParameterInstantiation');
@@ -2529,6 +3544,9 @@ function toSourceBabelTS(node) {
2529
3544
  type: 'object',
2530
3545
  properties
2531
3546
  };
3547
+ // case 'PropertySignature':
3548
+ // console.warn('toSourceBabelTS> should not happen, handled by TypeLiteral directly');
3549
+ // return `${toSourceBabelTS(node.name)}: ${toSourceBabelTS(node.type)}`;
2532
3550
  case 'Identifier':
2533
3551
  return node.name;
2534
3552
  case 'TSArrayType':
@@ -2538,25 +3556,36 @@ function toSourceBabelTS(node) {
2538
3556
  };
2539
3557
  case 'TSLiteralType':
2540
3558
  return toSourceBabelTS(node.literal);
3559
+ // expandTypeBabelTS("any")
2541
3560
  case 'TSAnyKeyword':
2542
3561
  return 'any';
3562
+ // expandTypeBabelTS("boolean")
2543
3563
  case 'TSBooleanKeyword':
2544
3564
  return 'boolean';
3565
+ // expandTypeBabelTS('true | false');
2545
3566
  case 'BooleanLiteral':
2546
3567
  return node.value.toString();
3568
+ // ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
3569
+ // But Babel-TS doesn't parse it atm
3570
+ //case 'JSDocAllType':
3571
+ // expandTypeBabelTS('null')
2547
3572
  case 'TSNullKeyword':
2548
3573
  return 'null';
3574
+ // expandTypeBabelTS('123')
2549
3575
  case 'NumericLiteral':
2550
3576
  case 'StringLiteral':
2551
3577
  return node.extra.raw;
3578
+ // expandTypeBabelTS('undefined')
2552
3579
  case 'TSUndefinedKeyword':
2553
3580
  return 'undefined';
2554
3581
  case 'TSUnknownKeyword':
2555
3582
  return 'unknown';
2556
3583
  case 'TSNeverKeyword':
2557
3584
  return 'never';
3585
+ // parseTypeBabelTS('void');
2558
3586
  case 'TSVoidKeyword':
2559
3587
  return 'void';
3588
+ // expandTypeBabelTS('this')
2560
3589
  case 'TSThisType':
2561
3590
  return 'this';
2562
3591
  case 'ObjectKeyword':
@@ -2565,6 +3594,7 @@ function toSourceBabelTS(node) {
2565
3594
  properties: {}
2566
3595
  };
2567
3596
  case 'ParenthesizedType':
3597
+ // fall-through for parentheses
2568
3598
  return toSourceBabelTS(node.type);
2569
3599
  case 'LastTypeNode':
2570
3600
  return toSourceBabelTS(node.qualifier);