@runtime-type-inspector/transpiler 3.2.6 → 3.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.cjs +264 -71
  2. package/index.mjs +307 -79
  3. package/package.json +2 -2
package/index.cjs CHANGED
@@ -282,24 +282,19 @@
282
282
  * expandType('typeof Number '); // Outputs:
283
283
  * @param {string} type - The type string to be expanded into a structured representation.
284
284
  * @todo Share type with expandTypeBabelTS and expandTypeDepFree
285
- * @returns {string | {type: string, [key: string]: any} | undefined} The structured type
285
+ * @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The structured type
286
286
  * representation obtained from parsing and converting the provided type string.
287
287
  */
288
288
  function expandType(type) {
289
289
  var ast = parseType(type);
290
+ if (!ast) {
291
+ return 'never';
292
+ }
290
293
  return toSourceTS(ast);
291
294
  }
292
- /**
293
- * @todo I want to use for example: import('typescript').Node
294
- * But the TS types make no sense to me so far ... need to investigate more.
295
- * @typedef TypeScriptType
296
- * @property {object[]|undefined} typeArguments - The type arguments.
297
- * @property {import('typescript').Node} typeName - The type name.
298
- * @property {number} kind - The kind for `ts.SyntaxKind[kind]`.
299
- */
300
295
  /**
301
296
  * @param {string} str - The type string.
302
- * @returns {TypeScriptType} - The node containing all the information about the input type string.
297
+ * @returns {ts.TypeNode|undefined} - The node containing all the information about the input type string.
303
298
  */
304
299
  function parseType(str) {
305
300
  // TS doesn't like ... notation in this context
@@ -310,7 +305,12 @@
310
305
  // type tmp = (...string) => 123; to have a function context
311
306
  str = "type tmp = ".concat(str, ";");
312
307
  var ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, true /*setParentNodes*/);
313
- return ast.statements[0].type;
308
+ var firstStatement = ast.statements[0];
309
+ if (!ts.isTypeAliasDeclaration(firstStatement)) {
310
+ console.warn('parseType> Expected type alias declaration, got', firstStatement, 'instead.');
311
+ return;
312
+ }
313
+ return firstStatement.type;
314
314
  }
315
315
  /** @type {Record<string, 'missing'|'found'>} */
316
316
  var requiredTypeofs = {};
@@ -320,7 +320,7 @@
320
320
  * This function handles various TypeScript AST node types and converts them into a string
321
321
  * or an object representing the type.
322
322
  *
323
- * @param {TypeScriptType} node - The TypeScript AST node to convert.
323
+ * @param {ts.TypeNode|ts.Identifier} node - The TypeScript AST node to convert.
324
324
  * @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The source string/number,
325
325
  * or an object with type information based on the node, or `undefined` if the node kind is not handled.
326
326
  */
@@ -336,7 +336,7 @@
336
336
  Identifier = _ts$SyntaxKind.Identifier,
337
337
  IntersectionType = _ts$SyntaxKind.IntersectionType,
338
338
  JSDocAllType = _ts$SyntaxKind.JSDocAllType,
339
- LastTypeNode = _ts$SyntaxKind.LastTypeNode,
339
+ ImportType = _ts$SyntaxKind.ImportType,
340
340
  LiteralType = _ts$SyntaxKind.LiteralType,
341
341
  NullKeyword = _ts$SyntaxKind.NullKeyword,
342
342
  NumberKeyword = _ts$SyntaxKind.NumberKeyword,
@@ -363,6 +363,7 @@
363
363
  BigIntLiteral = _ts$SyntaxKind.BigIntLiteral,
364
364
  ConditionalType = _ts$SyntaxKind.ConditionalType,
365
365
  IndexedAccessType = _ts$SyntaxKind.IndexedAccessType,
366
+ IndexSignature = _ts$SyntaxKind.IndexSignature,
366
367
  RestType = _ts$SyntaxKind.RestType,
367
368
  TypeQuery = _ts$SyntaxKind.TypeQuery,
368
369
  TypeOperator = _ts$SyntaxKind.TypeOperator,
@@ -378,12 +379,18 @@
378
379
  type: 'bigint'
379
380
  };
380
381
  case BigIntLiteral:
382
+ if (!ts.isBigIntLiteral(node)) {
383
+ throw Error("Impossible");
384
+ }
381
385
  var literal = node.text.slice(0, -1); // Remove the "n"
382
386
  return {
383
387
  type: 'bigint',
384
388
  literal: literal
385
389
  };
386
390
  case ConditionalType:
391
+ if (!ts.isConditionalTypeNode(node)) {
392
+ throw Error("Impossible");
393
+ }
387
394
  // Keys on node:
388
395
  // ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
389
396
  // 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
@@ -400,6 +407,9 @@
400
407
  };
401
408
  case ConstructorType:
402
409
  {
410
+ if (!ts.isConstructorTypeNode(node)) {
411
+ throw Error("Impossible");
412
+ }
403
413
  var _parameters = node.parameters.map(toSourceTS);
404
414
  var _ret = toSourceTS(node.type);
405
415
  return {
@@ -409,12 +419,18 @@
409
419
  };
410
420
  }
411
421
  case FunctionType:
422
+ if (!ts.isFunctionTypeNode(node)) {
423
+ throw Error("Impossible");
424
+ }
412
425
  var parameters = node.parameters.map(toSourceTS);
413
426
  return {
414
427
  type: 'function',
415
428
  parameters: parameters
416
429
  };
417
430
  case IndexedAccessType:
431
+ if (!ts.isIndexedAccessTypeNode(node)) {
432
+ throw Error("Impossible");
433
+ }
418
434
  var index = toSourceTS(node.indexType);
419
435
  var object = toSourceTS(node.objectType);
420
436
  return {
@@ -423,12 +439,18 @@
423
439
  object: object
424
440
  };
425
441
  case RestType:
442
+ if (!ts.isRestTypeNode(node)) {
443
+ throw Error("Impossible");
444
+ }
426
445
  var annotation = toSourceTS(node.type);
427
446
  return {
428
447
  type: 'rest',
429
448
  annotation: annotation
430
449
  };
431
450
  case JSDocNullableType:
451
+ if (!ts.isJSDocNullableType(node)) {
452
+ throw Error("Impossible");
453
+ }
432
454
  var t = toSourceTS(node.type);
433
455
  return {
434
456
  type: 'union',
@@ -436,6 +458,9 @@
436
458
  };
437
459
  case MappedType:
438
460
  {
461
+ if (!ts.isMappedTypeNode(node)) {
462
+ throw Error("Impossible");
463
+ }
439
464
  var result = toSourceTS(node.type);
440
465
  var parameter = node.typeParameter;
441
466
  if (parameter.kind === TypeParameter) {
@@ -455,6 +480,9 @@
455
480
  // todo work out more: const jsdoc = `(...a: ...number) => 123
456
481
  // TS even thinks it's two parameters... just go for array/[]
457
482
  case Parameter:
483
+ if (!ts.isParameter(node)) {
484
+ throw Error("Impossible");
485
+ }
458
486
  var type = node.type ? toSourceTS(node.type) : 'any';
459
487
  var name = toSourceTS(node.name);
460
488
  var ret = {
@@ -469,6 +497,9 @@
469
497
  }
470
498
  return ret;
471
499
  case TypeQuery:
500
+ if (!ts.isTypeQueryNode(node)) {
501
+ throw Error("Impossible");
502
+ }
472
503
  var argument = toSourceTS(node.exprName);
473
504
  // Notify Asserter class that we have to register variables with this name
474
505
  if (!requiredTypeofs[argument]) {
@@ -479,6 +510,9 @@
479
510
  argument: argument
480
511
  };
481
512
  case TypeOperator:
513
+ if (!ts.isTypeOperatorNode(node)) {
514
+ throw Error("Impossible");
515
+ }
482
516
  if (node.operator === KeyOfKeyword) {
483
517
  var _argument = toSourceTS(node.type);
484
518
  return {
@@ -489,6 +523,9 @@
489
523
  console.warn("unimplemented TypeOperator", node);
490
524
  case TypeReference:
491
525
  {
526
+ if (!ts.isTypeReferenceNode(node)) {
527
+ throw Error("Impossible");
528
+ }
492
529
  if ((typeName.text === 'Object' || typeName.text === 'Record') && (typeArguments === null || typeArguments === void 0 ? void 0 : typeArguments.length) === 2) {
493
530
  return {
494
531
  type: 'record',
@@ -544,14 +581,16 @@
544
581
  args: args
545
582
  };
546
583
  }
547
- case StringKeyword:
548
- return node.getText();
549
- case NumberKeyword:
550
- return node.getText();
551
584
  case NamedTupleMember:
585
+ if (!ts.isNamedTupleMember(node)) {
586
+ throw Error("Impossible");
587
+ }
552
588
  return toSourceTS(node.type);
553
589
  case IntersectionType:
554
590
  {
591
+ if (!ts.isIntersectionTypeNode(node)) {
592
+ throw Error("Impossible");
593
+ }
555
594
  var _members = node.types.map(toSourceTS);
556
595
  return {
557
596
  type: 'intersection',
@@ -559,35 +598,87 @@
559
598
  };
560
599
  }
561
600
  case TupleType:
601
+ if (!ts.isTupleTypeNode(node)) {
602
+ throw Error("Impossible");
603
+ }
562
604
  var elements = node.elements.map(toSourceTS);
563
605
  return {
564
606
  type: 'tuple',
565
607
  elements: elements
566
608
  };
567
609
  case UnionType:
610
+ if (!ts.isUnionTypeNode(node)) {
611
+ throw Error("Impossible");
612
+ }
568
613
  var members = node.types.map(toSourceTS);
569
614
  return {
570
615
  type: 'union',
571
616
  members: members
572
617
  };
573
618
  case TypeLiteral:
574
- var properties = {};
575
- node.members.forEach(function (member) {
576
- var name = toSourceTS(member.name);
577
- var type = toSourceTS(member.type);
578
- properties[name] = type;
579
- });
580
- return {
581
- type: 'object',
582
- properties: properties
583
- };
619
+ {
620
+ if (!ts.isTypeLiteralNode(node)) {
621
+ throw Error("Impossible");
622
+ }
623
+ var properties = {};
624
+ /** @type {object[]} */
625
+ var indexSignatures;
626
+ node.members.forEach(function (member) {
627
+ if (member.kind === IndexSignature) {
628
+ var _indexSignatures;
629
+ indexSignatures = (_indexSignatures = indexSignatures) !== null && _indexSignatures !== void 0 ? _indexSignatures : [];
630
+ indexSignatures.push(toSourceTS(member));
631
+ } else if (member.kind === PropertySignature) {
632
+ if (!ts.isPropertySignature(member)) {
633
+ throw Error("Impossible");
634
+ }
635
+ var _name2 = toSourceTS(member.name);
636
+ var _type = toSourceTS(member.type);
637
+ properties[_name2] = _type;
638
+ } else {
639
+ console.warn('TypeLiteral: unhandled member', member);
640
+ }
641
+ });
642
+ var _ret2 = {
643
+ type: 'object'
644
+ };
645
+ if (Object.keys(properties).length) {
646
+ _ret2.properties = properties;
647
+ }
648
+ if (indexSignatures) {
649
+ _ret2.indexSignatures = indexSignatures;
650
+ }
651
+ return _ret2;
652
+ }
584
653
  case PropertySignature:
654
+ if (!ts.isPropertySignature(node)) {
655
+ throw Error("Impossible");
656
+ }
585
657
  console.warn('toSourceTS> should not happen, handled by TypeLiteral directly');
586
658
  return "".concat(toSourceTS(node.name), ": ").concat(toSourceTS(node.type));
659
+ case IndexSignature:
660
+ if (!ts.isIndexSignatureDeclaration(node)) {
661
+ throw Error("Impossible");
662
+ }
663
+ // Only possible modifier I know of, but we don't need it:
664
+ // {readonly [n: number]: string, length: number}
665
+ var indexType = toSourceTS(node.type);
666
+ var indexParameters = node.parameters.map(toSourceTS);
667
+ return {
668
+ type: 'indexSignature',
669
+ indexType: indexType,
670
+ indexParameters: indexParameters
671
+ };
587
672
  case Identifier:
673
+ if (!ts.isIdentifier(node)) {
674
+ throw Error("Impossible");
675
+ }
588
676
  return node.text;
589
677
  case ArrayType:
590
678
  {
679
+ if (!ts.isArrayTypeNode(node)) {
680
+ throw Error("Impossible");
681
+ }
591
682
  var _elementType4 = toSourceTS(node.elementType);
592
683
  return {
593
684
  type: 'array',
@@ -595,18 +686,23 @@
595
686
  };
596
687
  }
597
688
  case LiteralType:
689
+ if (!ts.isLiteralTypeNode(node)) {
690
+ throw Error("Impossible");
691
+ }
598
692
  return toSourceTS(node.literal);
599
693
  case AnyKeyword:
600
694
  case BooleanKeyword:
601
- // ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
602
- case JSDocAllType:
695
+ case StringKeyword:
696
+ case NeverKeyword:
603
697
  case NullKeyword:
604
- case StringLiteral:
605
- case ThisType:
698
+ case NumberKeyword:
606
699
  case UndefinedKeyword:
607
- case VoidKeyword:
608
700
  case UnknownKeyword:
609
- case NeverKeyword:
701
+ case VoidKeyword:
702
+ // ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
703
+ case JSDocAllType:
704
+ case ThisType:
705
+ case StringLiteral:
610
706
  return node.getText();
611
707
  case TrueKeyword:
612
708
  return true;
@@ -620,9 +716,16 @@
620
716
  properties: {}
621
717
  };
622
718
  case ParenthesizedType:
719
+ if (!ts.isParenthesizedTypeNode(node)) {
720
+ throw Error("Impossible");
721
+ }
623
722
  // fall-through for parentheses
624
723
  return toSourceTS(node.type);
625
- case LastTypeNode:
724
+ case ImportType:
725
+ if (!ts.isImportTypeNode(node)) {
726
+ throw Error("Impossible");
727
+ }
728
+ /** @todo Handle case without any qualifier like `import('test')` */
626
729
  return toSourceTS(node.qualifier);
627
730
  default:
628
731
  // const test = {};
@@ -844,19 +947,26 @@
844
947
  return type;
845
948
  }
846
949
 
950
+ /**
951
+ * @typedef {ReturnType<typeof parseJSDoc>} ParseJSDocReturnType
952
+ */
953
+ /**
954
+ * @typedef {typeof expandTypeDepFree} ExpandType
955
+ * @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
956
+ */
847
957
  /**
848
958
  * Parses JSDoc comments to extract parameter type information.
849
959
  *
850
960
  * @param {string} src - The JSDoc comment string to parse.
851
- * @param {Function} [expandType] - An optional function to process the types found in the JSDoc.
852
- * @returns {Record<string, any> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
961
+ * @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
962
+ * @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
853
963
  */
854
964
  function parseJSDoc(src) {
855
965
  var expandType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : expandTypeDepFree;
856
966
  // Parse something like: @param {Object} [kwargs={}] Optional arguments.
857
967
  var regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\{\}\.'" ]+)/g;
858
968
  var matches = _toConsumableArray(src.matchAll(regex));
859
- /** @type {Record<string, any>} */
969
+ /** @type {Record<string, ExpandTypeReturnType>} */
860
970
  var params = Object.create(null);
861
971
  matches.forEach(function (_) {
862
972
  var type = expandType(_[1].trim());
@@ -884,7 +994,6 @@
884
994
  try {
885
995
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
886
996
  var part = _step.value;
887
- /** @type {object} */
888
997
  var toptype = properties[part];
889
998
  if (!toptype) {
890
999
  // No toptype means we resolved as far as possible, now we can add `simplifiedType`.
@@ -939,6 +1048,35 @@
939
1048
  }
940
1049
  }
941
1050
 
1051
+ /**
1052
+ * @typedef {typeof expandTypeDepFree} ExpandType
1053
+ * @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
1054
+ */
1055
+ /**
1056
+ * Parses JSDoc comments to extract parameter type information.
1057
+ *
1058
+ * @param {string} src - The JSDoc comment string to parse.
1059
+ * @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
1060
+ * @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping template names to their parsed types,
1061
+ * or `undefined` if no template tags were found.
1062
+ */
1063
+ function parseJSDocTemplates(src) {
1064
+ var expandType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : expandTypeDepFree;
1065
+ var regexTemplateTyped = /@template \{(.*?)\} ([a-zA-Z0-9_$=]+)/g;
1066
+ var matches = _toConsumableArray(src.matchAll(regexTemplateTyped));
1067
+ if (!matches.length) {
1068
+ return;
1069
+ }
1070
+ /** @type {Record<string, ExpandTypeReturnType>} */
1071
+ var templates = Object.create(null);
1072
+ matches.forEach(function (_) {
1073
+ var type = expandType(_[1].trim());
1074
+ var name = _[2].trim();
1075
+ templates[name] = type;
1076
+ });
1077
+ return templates;
1078
+ }
1079
+
942
1080
  /**
943
1081
  * Extracts the parameter name and its optionality from a JSDoc parameter string.
944
1082
  *
@@ -2943,14 +3081,14 @@
2943
3081
  }();
2944
3082
 
2945
3083
  /** @typedef {import('@babel/types').Node } Node */
2946
- /** @typedef {import("@babel/types").ClassMethod } ClassMethod */
2947
- /** @typedef {import("@babel/types").ClassPrivateMethod} ClassPrivateMethod */
2948
- /** @typedef {import('./stat.js').Stat } Stat */
3084
+ /** @typedef {import('@babel/types').ClassMethod } ClassMethod */
3085
+ /** @typedef {import('@babel/types').ClassPrivateMethod} ClassPrivateMethod */
3086
+ /** @typedef {import('./stat.js').Stat } Stat */
2949
3087
  /**
2950
3088
  * @typedef {object} Options
2951
3089
  * @property {boolean} [forceCurly] - Determines whether curly braces are enforced in Stringifier.
2952
3090
  * @property {boolean} [validateDivision] - Indicates whether division operations should be validated.
2953
- * @property {Function} [expandType] - A function that expands shorthand types into full descriptions.
3091
+ * @property {import('./parseJSDoc.js').ExpandType} [expandType] - A function that expands shorthand types into full descriptions.
2954
3092
  * @property {string} [filename] - The name of a file to which the instance pertains.
2955
3093
  * @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
2956
3094
  * @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
@@ -3227,11 +3365,11 @@
3227
3365
  }
3228
3366
  /**
3229
3367
  * @param {Node} node - The Babel AST node.
3230
- * @returns {undefined | {}} The return value of `parseJSDoc`.
3368
+ * @returns {string|undefined} The JSDoc comment of `node`.
3231
3369
  */
3232
3370
  }, {
3233
- key: "getJSDoc",
3234
- value: function getJSDoc(node) {
3371
+ key: "getLeadingComment",
3372
+ value: function getLeadingComment(node) {
3235
3373
  if (node.type === 'BlockStatement') {
3236
3374
  node = this.parent;
3237
3375
  }
@@ -3256,27 +3394,57 @@
3256
3394
  if (leadingComments && leadingComments.length) {
3257
3395
  var lastComment = leadingComments[leadingComments.length - 1];
3258
3396
  if (lastComment.type === "CommentBlock") {
3259
- if (lastComment.value.includes('@event')) {
3260
- return;
3261
- }
3262
- if (node.type === 'ClassMethod' && node.kind === 'set') {
3263
- var paramName = this.getNameOfParam(node.params[0]);
3264
- if (node.params.length !== 1) {
3265
- this.warn("getJSDoc> setters require exactly one argument");
3266
- }
3267
- var setterType = parseJSDocSetter(lastComment.value, this.expandType);
3268
- if (!setterType) {
3269
- return;
3270
- }
3271
- return _defineProperty({}, paramName, setterType);
3272
- }
3273
- if (lastComment.value.includes('@ignoreRTI')) {
3274
- return;
3275
- }
3276
- return parseJSDoc(lastComment.value, this.expandType);
3397
+ return lastComment.value;
3277
3398
  }
3278
3399
  }
3279
3400
  }
3401
+ /**
3402
+ * @param {Node} node - The Babel AST node.
3403
+ * @todo ESLint problem:
3404
+ * returns {import('./parseJSDoc.js').ParseJSDocReturnType} The return value of `parseJSDoc`
3405
+ * returns {Record<string, import('./parseJSDoc.js').ExpandTypeReturnType> | undefined} The
3406
+ * return value of `parseJSDoc`.
3407
+ * @returns {Record<string, any>|undefined} asd
3408
+ */
3409
+ }, {
3410
+ key: "getJSDoc",
3411
+ value: function getJSDoc(node) {
3412
+ var comment = this.getLeadingComment(node);
3413
+ if (!comment) {
3414
+ return;
3415
+ }
3416
+ if (comment.includes('@event')) {
3417
+ return;
3418
+ }
3419
+ if (comment.includes('@ignoreRTI')) {
3420
+ return;
3421
+ }
3422
+ // Need to do same resolving as in: this.getLeadingComment(node)
3423
+ if (node.type === 'BlockStatement') {
3424
+ node = this.parent;
3425
+ }
3426
+ if (node.type === 'ClassMethod' && node.kind === 'set') {
3427
+ var paramName = this.getNameOfParam(node.params[0]);
3428
+ if (node.params.length !== 1) {
3429
+ this.warn("getJSDoc> setters require exactly one argument");
3430
+ }
3431
+ var setterType = parseJSDocSetter(comment, this.expandType);
3432
+ if (!setterType) {
3433
+ return;
3434
+ }
3435
+ var _params = _defineProperty({}, paramName, setterType);
3436
+ return {
3437
+ templates: undefined,
3438
+ params: _params
3439
+ };
3440
+ }
3441
+ var templates = parseJSDocTemplates(comment);
3442
+ var params = parseJSDoc(comment, this.expandType);
3443
+ return {
3444
+ templates: templates,
3445
+ params: params
3446
+ };
3447
+ }
3280
3448
  /**
3281
3449
  * Retrieves the name of a parameter from a Babel AST node.
3282
3450
  *
@@ -3296,8 +3464,7 @@
3296
3464
  return param.left.name;
3297
3465
  }
3298
3466
  }
3299
- debugger;
3300
- this.warn("unable to extra name from param in specified way - may contain too much information");
3467
+ this.warn("Unable to retrieve name from param in specified way - may contain too much information.");
3301
3468
  return this.toSource(param);
3302
3469
  }
3303
3470
  }, {
@@ -3414,6 +3581,15 @@
3414
3581
  stat.unchecked++;
3415
3582
  return '';
3416
3583
  }
3584
+ var templates = jsdoc.templates,
3585
+ params = jsdoc.params;
3586
+ if (!params) {
3587
+ console.warn("This should never happen, please check your input code.", this.getLeadingComment(node), {
3588
+ jsdoc: jsdoc
3589
+ });
3590
+ stat.unchecked++;
3591
+ return '';
3592
+ }
3417
3593
  stat.checked++;
3418
3594
  var spaces = this.spaces;
3419
3595
  var out = '';
@@ -3422,17 +3598,21 @@
3422
3598
  if (this.ignoreLocations.includes(loc)) {
3423
3599
  return '// IGNORE RTI TYPE VALIDATIONS, KNOWN ISSUES\n';
3424
3600
  }
3601
+ if (templates) {
3602
+ var tmp = JSON.stringify(templates, null, 2).replaceAll('\n', '\n' + spaces);
3603
+ out += "\n".concat(spaces, "const rtiTemplates = ").concat(tmp, ";");
3604
+ }
3425
3605
  //out += `${spaces}/*${spaces} node.type=${node.type}\n${spaces}
3426
3606
  // ${JSON.stringify(jsdoc)}\n${parent}\n${spaces}*/\n`;
3427
3607
  var _loop = function _loop(name) {
3428
- var type = jsdoc[name];
3608
+ var type = params[name];
3429
3609
  var hasParam = _this2.nodeHasParamName(node, name);
3430
3610
  if (!hasParam) {
3431
3611
  var testNode = node;
3432
3612
  if (node.type === 'BlockStatement') {
3433
3613
  testNode = _this2.parent;
3434
3614
  }
3435
- var paramIndex = Object.keys(jsdoc).findIndex(function (_) {
3615
+ var paramIndex = Object.keys(params).findIndex(function (_) {
3436
3616
  return _ === name;
3437
3617
  });
3438
3618
  var param = testNode.params[paramIndex];
@@ -3468,7 +3648,11 @@
3468
3648
  continue;
3469
3649
  }
3470
3650
  var _t = JSON.stringify(type.elementType, null, 2).replaceAll('\n', '\n' + spaces);
3471
- out += "".concat(spaces, "if (!inspectType(").concat(element.name, ", ").concat(_t, ", '").concat(_loc, "', '").concat(name, "')) {\n");
3651
+ if (templates) {
3652
+ out += "".concat(spaces, "if (!inspectTypeWithTemplates(").concat(element.name, ", ").concat(_t, ", '").concat(_loc, "', '").concat(name, "', rtiTemplates)) {\n");
3653
+ } else {
3654
+ out += "".concat(spaces, "if (!inspectType(").concat(element.name, ", ").concat(_t, ", '").concat(_loc, "', '").concat(name, "')) {\n");
3655
+ }
3472
3656
  out += "".concat(spaces, " youCanAddABreakpointHere();\n").concat(spaces, "}\n");
3473
3657
  }
3474
3658
  } catch (err) {
@@ -3499,7 +3683,11 @@
3499
3683
  continue;
3500
3684
  }
3501
3685
  var _t2 = JSON.stringify(subType, null, 2).replaceAll('\n', '\n' + spaces);
3502
- out += "".concat(spaces, "if (!inspectType(").concat(keyName, ", ").concat(_t2, ", '").concat(_loc, "', '").concat(name, "')) {\n");
3686
+ if (templates) {
3687
+ out += "".concat(spaces, "if (!inspectTypeWithTemplates(").concat(keyName, ", ").concat(_t2, ", '").concat(_loc, "', '").concat(name, "', rtiTemplates)) {\n");
3688
+ } else {
3689
+ out += "".concat(spaces, "if (!inspectType(").concat(keyName, ", ").concat(_t2, ", '").concat(_loc, "', '").concat(name, "')) {\n");
3690
+ }
3503
3691
  out += "".concat(spaces, " youCanAddABreakpointHere();\n").concat(spaces, "}\n");
3504
3692
  }
3505
3693
  } catch (err) {
@@ -3537,11 +3725,15 @@
3537
3725
  out += '\n';
3538
3726
  first = false;
3539
3727
  }
3540
- out += "".concat(spaces, "if (").concat(prevCheck, "!inspectType(").concat(name, ", ").concat(t, ", '").concat(loc, "', '").concat(name, "')) {\n");
3728
+ if (templates) {
3729
+ out += "".concat(spaces, "if (").concat(prevCheck, "!inspectTypeWithTemplates(").concat(name, ", ").concat(t, ", '").concat(loc, "', '").concat(name, "', rtiTemplates)) {\n");
3730
+ } else {
3731
+ out += "".concat(spaces, "if (").concat(prevCheck, "!inspectType(").concat(name, ", ").concat(t, ", '").concat(loc, "', '").concat(name, "')) {\n");
3732
+ }
3541
3733
  out += "".concat(spaces, " youCanAddABreakpointHere();\n").concat(spaces, "}\n");
3542
3734
  },
3543
3735
  _ret;
3544
- for (var name in jsdoc) {
3736
+ for (var name in params) {
3545
3737
  _ret = _loop(name);
3546
3738
  if (_ret === 0) continue;
3547
3739
  }
@@ -4119,6 +4311,7 @@
4119
4311
  exports.nodeIsFunction = nodeIsFunction;
4120
4312
  exports.parseJSDoc = parseJSDoc;
4121
4313
  exports.parseJSDocSetter = parseJSDocSetter;
4314
+ exports.parseJSDocTemplates = parseJSDocTemplates;
4122
4315
  exports.parseJSDocTypedef = parseJSDocTypedef;
4123
4316
  exports.parseType = parseType;
4124
4317
  exports.parseTypeBabelTS = parseTypeBabelTS;
package/index.mjs CHANGED
@@ -25,24 +25,19 @@ import ts from 'typescript';
25
25
  * expandType('typeof Number '); // Outputs:
26
26
  * @param {string} type - The type string to be expanded into a structured representation.
27
27
  * @todo Share type with expandTypeBabelTS and expandTypeDepFree
28
- * @returns {string | {type: string, [key: string]: any} | undefined} The structured type
28
+ * @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The structured type
29
29
  * representation obtained from parsing and converting the provided type string.
30
30
  */
31
31
  function expandType(type) {
32
32
  const ast = parseType(type);
33
+ if (!ast) {
34
+ return 'never';
35
+ }
33
36
  return toSourceTS(ast);
34
37
  }
35
- /**
36
- * @todo I want to use for example: import('typescript').Node
37
- * But the TS types make no sense to me so far ... need to investigate more.
38
- * @typedef TypeScriptType
39
- * @property {object[]|undefined} typeArguments - The type arguments.
40
- * @property {import('typescript').Node} typeName - The type name.
41
- * @property {number} kind - The kind for `ts.SyntaxKind[kind]`.
42
- */
43
38
  /**
44
39
  * @param {string} str - The type string.
45
- * @returns {TypeScriptType} - The node containing all the information about the input type string.
40
+ * @returns {ts.TypeNode|undefined} - The node containing all the information about the input type string.
46
41
  */
47
42
  function parseType(str) {
48
43
  // TS doesn't like ... notation in this context
@@ -53,7 +48,12 @@ function parseType(str) {
53
48
  // type tmp = (...string) => 123; to have a function context
54
49
  str = `type tmp = ${str};`;
55
50
  const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, true /*setParentNodes*/);
56
- return ast.statements[0].type;
51
+ const firstStatement = ast.statements[0];
52
+ if (!ts.isTypeAliasDeclaration(firstStatement)) {
53
+ console.warn('parseType> Expected type alias declaration, got', firstStatement, 'instead.');
54
+ return;
55
+ }
56
+ return firstStatement.type;
57
57
  }
58
58
  /** @type {Record<string, 'missing'|'found'>} */
59
59
  const requiredTypeofs = {};
@@ -63,7 +63,7 @@ const requiredTypeofs = {};
63
63
  * This function handles various TypeScript AST node types and converts them into a string
64
64
  * or an object representing the type.
65
65
  *
66
- * @param {TypeScriptType} node - The TypeScript AST node to convert.
66
+ * @param {ts.TypeNode|ts.Identifier} node - The TypeScript AST node to convert.
67
67
  * @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The source string/number,
68
68
  * or an object with type information based on the node, or `undefined` if the node kind is not handled.
69
69
  */
@@ -75,52 +75,90 @@ function toSourceTS(node) {
75
75
  const kind_ = ts.SyntaxKind[node.kind];
76
76
  const {
77
77
  AnyKeyword,
78
+ // parseType('any' ).kind === ts.SyntaxKind.AnyKeyword && toSourceTS(parseType('any')) === 'any'
78
79
  ArrayType,
80
+ // parseType('number[]' ).kind === ts.SyntaxKind.ArrayType // todo toSourceTS(parseType('number[]')) === {type: 'array etc.
79
81
  BooleanKeyword,
82
+ // parseType("boolean" ).kind === ts.SyntaxKind.BooleanKeyword
80
83
  FunctionType,
84
+ // parseType("() => void" ).kind === ts.SyntaxKind.FunctionType
81
85
  Identifier,
86
+ // parseType("{a: 1, b: 2}" ).members[0].name.kind === ts.SyntaxKind.Identifier
82
87
  IntersectionType,
88
+ // parseType("1 & 2" ).kind === ts.SyntaxKind.IntersectionType
83
89
  JSDocAllType,
84
- LastTypeNode,
90
+ // parseType("*" ).kind === ts.SyntaxKind.JSDocAllType
91
+ ImportType,
92
+ // parseType('import("test").Test' ).kind === ts.SyntaxKind.ImportType
85
93
  LiteralType,
94
+ // parseType("123" ).kind === ts.SyntaxKind.LiteralType
86
95
  NullKeyword,
96
+ // parseType("null" ).literal.kind === ts.SyntaxKind.NullKeyword
87
97
  NumberKeyword,
98
+ // parseType("number" ).kind === ts.SyntaxKind.NumberKeyword
88
99
  NumericLiteral,
100
+ // parseType("123" ).literal.kind === ts.SyntaxKind.NumericLiteral
89
101
  ObjectKeyword,
102
+ // parseType("object" ).kind === ts.SyntaxKind.ObjectKeyword
90
103
  Parameter,
104
+ // parseType("(a) => void" ).parameters[0].kind === ts.SyntaxKind.Parameter
91
105
  ParenthesizedType,
106
+ // parseType("(SomeType)" ).kind === ts.SyntaxKind.ParenthesizedType
92
107
  PropertySignature,
108
+ // parseType("{a: 1, b: 2}" ).members[0].kind === ts.SyntaxKind.PropertySignature
93
109
  StringKeyword,
110
+ // parseType("string" ).kind === ts.SyntaxKind.StringKeyword
94
111
  StringLiteral,
112
+ // parseType("'test'" ).literal.kind === ts.SyntaxKind.StringLiteral
95
113
  ThisType,
114
+ // parseType("this" ).kind === ts.SyntaxKind.ThisType
96
115
  TupleType,
116
+ // parseType("[1, 2, 3]" ).kind === ts.SyntaxKind.TupleType
97
117
  TypeLiteral,
118
+ // parseType("{a: 1, b: 2}" ).kind === ts.SyntaxKind.TypeLiteral
98
119
  TypeReference,
120
+ // parseType("SomeOtherType" ).kind === ts.SyntaxKind.TypeReference
99
121
  UndefinedKeyword,
122
+ // parseType("undefined" ).kind === ts.SyntaxKind.UndefinedKeyword
100
123
  UnionType,
124
+ // parseType("1|2" ).kind === ts.SyntaxKind.UnionType
101
125
  JSDocNullableType,
126
+ // parseType("?lol?" ).kind === ts.SyntaxKind.JSDocNullableType
102
127
  TrueKeyword,
128
+ // parseType("true" ).literal.kind === ts.SyntaxKind.TrueKeyword
103
129
  FalseKeyword,
130
+ // parseType("false" ).literal.kind === ts.SyntaxKind.FalseKeyword
104
131
  VoidKeyword,
132
+ // parseType("void" ).kind === ts.SyntaxKind.VoidKeyword
105
133
  UnknownKeyword,
134
+ // parseType("unknown" ).kind === ts.SyntaxKind.UnknownKeyword
106
135
  NeverKeyword,
136
+ // parseType("never" ).kind === ts.SyntaxKind.NeverKeyword
107
137
  BigIntKeyword,
138
+ // parseType("bigint" ).kind === ts.SyntaxKind.BigIntKeyword
108
139
  BigIntLiteral,
140
+ // parseType("123n" ).literal.kind === ts.SyntaxKind.BigIntLiteral
109
141
  ConditionalType,
142
+ // parseType("1 extends number ? true : false").kind === ts.SyntaxKind.ConditionalType
110
143
  IndexedAccessType,
144
+ // parseType('Test[123]' ).kind === ts.SyntaxKind.IndexedAccessType
145
+ IndexSignature,
146
+ // parseType('{[n: number]: string}' ).members[0].kind === ts.SyntaxKind.IndexSignature
111
147
  RestType,
148
+ // parseType("[...number]" ).elements[0].kind === ts.SyntaxKind.RestType
112
149
  TypeQuery,
113
- // parseType('typeof Number')
150
+ // parseType('typeof Number' ).kind === ts.SyntaxKind.TypeQuery
114
151
  TypeOperator,
115
- // parseType('keyof typeof obj')
152
+ // parseType('keyof typeof obj' ).kind === ts.SyntaxKind.TypeOperator
116
153
  KeyOfKeyword,
117
- // "operator" key in TypeOperator node
154
+ // parseType('keyof typeof obj' ).operator === ts.SyntaxKind.KeyOfKeyword
118
155
  ConstructorType,
119
- // parseType('new (...args: any[]) => any');
156
+ // parseType('new (...args: any[]) => any' ).kind === ts.SyntaxKind.ConstructorType
120
157
  NamedTupleMember,
158
+ // parseType('[a: 1]' ).elements[0].kind === ts.SyntaxKind.NamedTupleMember
121
159
  MappedType,
122
- // parseType('{[K in TaskType]: InstanceType<typeof SUPPORTED_TASKS[K]["pipeline"]>}')
123
- TypeParameter // Basically K and TaskType of MappedType
160
+ // parseType('{[K in TaskType]: 123}' ).kind === ts.SyntaxKind.MappedType
161
+ TypeParameter // parseType('{[K in TaskType]: 123}' ).typeParameter.kind === ts.SyntaxKind.TypeParameter
124
162
  } = ts.SyntaxKind;
125
163
  // console.log({typeArguments, typeName, kind_, node});
126
164
  switch (node.kind) {
@@ -129,12 +167,18 @@ function toSourceTS(node) {
129
167
  type: 'bigint'
130
168
  };
131
169
  case BigIntLiteral:
170
+ if (!ts.isBigIntLiteral(node)) {
171
+ throw Error("Impossible");
172
+ }
132
173
  const literal = node.text.slice(0, -1); // Remove the "n"
133
174
  return {
134
175
  type: 'bigint',
135
176
  literal
136
177
  };
137
178
  case ConditionalType:
179
+ if (!ts.isConditionalTypeNode(node)) {
180
+ throw Error("Impossible");
181
+ }
138
182
  // Keys on node:
139
183
  // ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
140
184
  // 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
@@ -151,6 +195,9 @@ function toSourceTS(node) {
151
195
  };
152
196
  case ConstructorType:
153
197
  {
198
+ if (!ts.isConstructorTypeNode(node)) {
199
+ throw Error("Impossible");
200
+ }
154
201
  const _parameters = node.parameters.map(toSourceTS);
155
202
  const _ret = toSourceTS(node.type);
156
203
  return {
@@ -160,12 +207,18 @@ function toSourceTS(node) {
160
207
  };
161
208
  }
162
209
  case FunctionType:
210
+ if (!ts.isFunctionTypeNode(node)) {
211
+ throw Error("Impossible");
212
+ }
163
213
  const parameters = node.parameters.map(toSourceTS);
164
214
  return {
165
215
  type: 'function',
166
216
  parameters
167
217
  };
168
218
  case IndexedAccessType:
219
+ if (!ts.isIndexedAccessTypeNode(node)) {
220
+ throw Error("Impossible");
221
+ }
169
222
  const index = toSourceTS(node.indexType);
170
223
  const object = toSourceTS(node.objectType);
171
224
  return {
@@ -174,12 +227,18 @@ function toSourceTS(node) {
174
227
  object
175
228
  };
176
229
  case RestType:
230
+ if (!ts.isRestTypeNode(node)) {
231
+ throw Error("Impossible");
232
+ }
177
233
  const annotation = toSourceTS(node.type);
178
234
  return {
179
235
  type: 'rest',
180
236
  annotation
181
237
  };
182
238
  case JSDocNullableType:
239
+ if (!ts.isJSDocNullableType(node)) {
240
+ throw Error("Impossible");
241
+ }
183
242
  const t = toSourceTS(node.type);
184
243
  return {
185
244
  type: 'union',
@@ -187,6 +246,9 @@ function toSourceTS(node) {
187
246
  };
188
247
  case MappedType:
189
248
  {
249
+ if (!ts.isMappedTypeNode(node)) {
250
+ throw Error("Impossible");
251
+ }
190
252
  const result = toSourceTS(node.type);
191
253
  const parameter = node.typeParameter;
192
254
  if (parameter.kind === TypeParameter) {
@@ -206,6 +268,9 @@ function toSourceTS(node) {
206
268
  // todo work out more: const jsdoc = `(...a: ...number) => 123
207
269
  // TS even thinks it's two parameters... just go for array/[]
208
270
  case Parameter:
271
+ if (!ts.isParameter(node)) {
272
+ throw Error("Impossible");
273
+ }
209
274
  const type = node.type ? toSourceTS(node.type) : 'any';
210
275
  const name = toSourceTS(node.name);
211
276
  const ret = {
@@ -220,6 +285,9 @@ function toSourceTS(node) {
220
285
  }
221
286
  return ret;
222
287
  case TypeQuery:
288
+ if (!ts.isTypeQueryNode(node)) {
289
+ throw Error("Impossible");
290
+ }
223
291
  const argument = toSourceTS(node.exprName);
224
292
  // Notify Asserter class that we have to register variables with this name
225
293
  if (!requiredTypeofs[argument]) {
@@ -230,6 +298,9 @@ function toSourceTS(node) {
230
298
  argument
231
299
  };
232
300
  case TypeOperator:
301
+ if (!ts.isTypeOperatorNode(node)) {
302
+ throw Error("Impossible");
303
+ }
233
304
  if (node.operator === KeyOfKeyword) {
234
305
  const _argument = toSourceTS(node.type);
235
306
  return {
@@ -240,6 +311,9 @@ function toSourceTS(node) {
240
311
  console.warn("unimplemented TypeOperator", node);
241
312
  case TypeReference:
242
313
  {
314
+ if (!ts.isTypeReferenceNode(node)) {
315
+ throw Error("Impossible");
316
+ }
243
317
  if ((typeName.text === 'Object' || typeName.text === 'Record') && (typeArguments == null ? void 0 : typeArguments.length) === 2) {
244
318
  return {
245
319
  type: 'record',
@@ -295,14 +369,16 @@ function toSourceTS(node) {
295
369
  args
296
370
  };
297
371
  }
298
- case StringKeyword:
299
- return node.getText();
300
- case NumberKeyword:
301
- return node.getText();
302
372
  case NamedTupleMember:
373
+ if (!ts.isNamedTupleMember(node)) {
374
+ throw Error("Impossible");
375
+ }
303
376
  return toSourceTS(node.type);
304
377
  case IntersectionType:
305
378
  {
379
+ if (!ts.isIntersectionTypeNode(node)) {
380
+ throw Error("Impossible");
381
+ }
306
382
  const _members = node.types.map(toSourceTS);
307
383
  return {
308
384
  type: 'intersection',
@@ -310,35 +386,87 @@ function toSourceTS(node) {
310
386
  };
311
387
  }
312
388
  case TupleType:
389
+ if (!ts.isTupleTypeNode(node)) {
390
+ throw Error("Impossible");
391
+ }
313
392
  const elements = node.elements.map(toSourceTS);
314
393
  return {
315
394
  type: 'tuple',
316
395
  elements
317
396
  };
318
397
  case UnionType:
398
+ if (!ts.isUnionTypeNode(node)) {
399
+ throw Error("Impossible");
400
+ }
319
401
  const members = node.types.map(toSourceTS);
320
402
  return {
321
403
  type: 'union',
322
404
  members
323
405
  };
324
406
  case TypeLiteral:
325
- const properties = {};
326
- node.members.forEach(member => {
327
- const name = toSourceTS(member.name);
328
- const type = toSourceTS(member.type);
329
- properties[name] = type;
330
- });
331
- return {
332
- type: 'object',
333
- properties
334
- };
407
+ {
408
+ if (!ts.isTypeLiteralNode(node)) {
409
+ throw Error("Impossible");
410
+ }
411
+ const properties = {};
412
+ /** @type {object[]} */
413
+ let indexSignatures;
414
+ node.members.forEach(member => {
415
+ if (member.kind === IndexSignature) {
416
+ var _indexSignatures;
417
+ indexSignatures = (_indexSignatures = indexSignatures) != null ? _indexSignatures : [];
418
+ indexSignatures.push(toSourceTS(member));
419
+ } else if (member.kind === PropertySignature) {
420
+ if (!ts.isPropertySignature(member)) {
421
+ throw Error("Impossible");
422
+ }
423
+ const name = toSourceTS(member.name);
424
+ const type = toSourceTS(member.type);
425
+ properties[name] = type;
426
+ } else {
427
+ console.warn('TypeLiteral: unhandled member', member);
428
+ }
429
+ });
430
+ const _ret2 = {
431
+ type: 'object'
432
+ };
433
+ if (Object.keys(properties).length) {
434
+ _ret2.properties = properties;
435
+ }
436
+ if (indexSignatures) {
437
+ _ret2.indexSignatures = indexSignatures;
438
+ }
439
+ return _ret2;
440
+ }
335
441
  case PropertySignature:
442
+ if (!ts.isPropertySignature(node)) {
443
+ throw Error("Impossible");
444
+ }
336
445
  console.warn('toSourceTS> should not happen, handled by TypeLiteral directly');
337
446
  return `${toSourceTS(node.name)}: ${toSourceTS(node.type)}`;
447
+ case IndexSignature:
448
+ if (!ts.isIndexSignatureDeclaration(node)) {
449
+ throw Error("Impossible");
450
+ }
451
+ // Only possible modifier I know of, but we don't need it:
452
+ // {readonly [n: number]: string, length: number}
453
+ const indexType = toSourceTS(node.type);
454
+ const indexParameters = node.parameters.map(toSourceTS);
455
+ return {
456
+ type: 'indexSignature',
457
+ indexType,
458
+ indexParameters
459
+ };
338
460
  case Identifier:
461
+ if (!ts.isIdentifier(node)) {
462
+ throw Error("Impossible");
463
+ }
339
464
  return node.text;
340
465
  case ArrayType:
341
466
  {
467
+ if (!ts.isArrayTypeNode(node)) {
468
+ throw Error("Impossible");
469
+ }
342
470
  const elementType = toSourceTS(node.elementType);
343
471
  return {
344
472
  type: 'array',
@@ -346,18 +474,23 @@ function toSourceTS(node) {
346
474
  };
347
475
  }
348
476
  case LiteralType:
477
+ if (!ts.isLiteralTypeNode(node)) {
478
+ throw Error("Impossible");
479
+ }
349
480
  return toSourceTS(node.literal);
350
481
  case AnyKeyword:
351
482
  case BooleanKeyword:
352
- // ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
353
- case JSDocAllType:
483
+ case StringKeyword:
484
+ case NeverKeyword:
354
485
  case NullKeyword:
355
- case StringLiteral:
356
- case ThisType:
486
+ case NumberKeyword:
357
487
  case UndefinedKeyword:
358
- case VoidKeyword:
359
488
  case UnknownKeyword:
360
- case NeverKeyword:
489
+ case VoidKeyword:
490
+ // ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
491
+ case JSDocAllType:
492
+ case ThisType:
493
+ case StringLiteral:
361
494
  return node.getText();
362
495
  case TrueKeyword:
363
496
  return true;
@@ -371,9 +504,16 @@ function toSourceTS(node) {
371
504
  properties: {}
372
505
  };
373
506
  case ParenthesizedType:
507
+ if (!ts.isParenthesizedTypeNode(node)) {
508
+ throw Error("Impossible");
509
+ }
374
510
  // fall-through for parentheses
375
511
  return toSourceTS(node.type);
376
- case LastTypeNode:
512
+ case ImportType:
513
+ if (!ts.isImportTypeNode(node)) {
514
+ throw Error("Impossible");
515
+ }
516
+ /** @todo Handle case without any qualifier like `import('test')` */
377
517
  return toSourceTS(node.qualifier);
378
518
  default:
379
519
  // const test = {};
@@ -588,18 +728,25 @@ function simplifyType(type, optional) {
588
728
  return type;
589
729
  }
590
730
 
731
+ /**
732
+ * @typedef {ReturnType<typeof parseJSDoc>} ParseJSDocReturnType
733
+ */
734
+ /**
735
+ * @typedef {typeof expandTypeDepFree} ExpandType
736
+ * @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
737
+ */
591
738
  /**
592
739
  * Parses JSDoc comments to extract parameter type information.
593
740
  *
594
741
  * @param {string} src - The JSDoc comment string to parse.
595
- * @param {Function} [expandType] - An optional function to process the types found in the JSDoc.
596
- * @returns {Record<string, any> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
742
+ * @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
743
+ * @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
597
744
  */
598
745
  function parseJSDoc(src, expandType = expandTypeDepFree) {
599
746
  // Parse something like: @param {Object} [kwargs={}] Optional arguments.
600
747
  const regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\{\}\.'" ]+)/g;
601
748
  const matches = [...src.matchAll(regex)];
602
- /** @type {Record<string, any>} */
749
+ /** @type {Record<string, ExpandTypeReturnType>} */
603
750
  const params = Object.create(null);
604
751
  matches.forEach(_ => {
605
752
  const type = expandType(_[1].trim());
@@ -623,7 +770,6 @@ function parseJSDoc(src, expandType = expandTypeDepFree) {
623
770
  const parts = name.split(/[\[\]]*\./);
624
771
  let properties = params;
625
772
  for (const part of parts) {
626
- /** @type {object} */
627
773
  const toptype = properties[part];
628
774
  if (!toptype) {
629
775
  // No toptype means we resolved as far as possible, now we can add `simplifiedType`.
@@ -670,6 +816,34 @@ function parseJSDocSetter(src, expandType = expandTypeDepFree) {
670
816
  }
671
817
  }
672
818
 
819
+ /**
820
+ * @typedef {typeof expandTypeDepFree} ExpandType
821
+ * @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
822
+ */
823
+ /**
824
+ * Parses JSDoc comments to extract parameter type information.
825
+ *
826
+ * @param {string} src - The JSDoc comment string to parse.
827
+ * @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
828
+ * @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping template names to their parsed types,
829
+ * or `undefined` if no template tags were found.
830
+ */
831
+ function parseJSDocTemplates(src, expandType = expandTypeDepFree) {
832
+ const regexTemplateTyped = /@template \{(.*?)\} ([a-zA-Z0-9_$=]+)/g;
833
+ const matches = [...src.matchAll(regexTemplateTyped)];
834
+ if (!matches.length) {
835
+ return;
836
+ }
837
+ /** @type {Record<string, ExpandTypeReturnType>} */
838
+ const templates = Object.create(null);
839
+ matches.forEach(_ => {
840
+ const type = expandType(_[1].trim());
841
+ const name = _[2].trim();
842
+ templates[name] = type;
843
+ });
844
+ return templates;
845
+ }
846
+
673
847
  /**
674
848
  * Extracts the parameter name and its optionality from a JSDoc parameter string.
675
849
  *
@@ -2619,14 +2793,14 @@ class Stringifier {
2619
2793
  }
2620
2794
 
2621
2795
  /** @typedef {import('@babel/types').Node } Node */
2622
- /** @typedef {import("@babel/types").ClassMethod } ClassMethod */
2623
- /** @typedef {import("@babel/types").ClassPrivateMethod} ClassPrivateMethod */
2624
- /** @typedef {import('./stat.js').Stat } Stat */
2796
+ /** @typedef {import('@babel/types').ClassMethod } ClassMethod */
2797
+ /** @typedef {import('@babel/types').ClassPrivateMethod} ClassPrivateMethod */
2798
+ /** @typedef {import('./stat.js').Stat } Stat */
2625
2799
  /**
2626
2800
  * @typedef {object} Options
2627
2801
  * @property {boolean} [forceCurly] - Determines whether curly braces are enforced in Stringifier.
2628
2802
  * @property {boolean} [validateDivision] - Indicates whether division operations should be validated.
2629
- * @property {Function} [expandType] - A function that expands shorthand types into full descriptions.
2803
+ * @property {import('./parseJSDoc.js').ExpandType} [expandType] - A function that expands shorthand types into full descriptions.
2630
2804
  * @property {string} [filename] - The name of a file to which the instance pertains.
2631
2805
  * @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
2632
2806
  * @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
@@ -2879,9 +3053,9 @@ class Asserter extends Stringifier {
2879
3053
  }
2880
3054
  /**
2881
3055
  * @param {Node} node - The Babel AST node.
2882
- * @returns {undefined | {}} The return value of `parseJSDoc`.
3056
+ * @returns {string|undefined} The JSDoc comment of `node`.
2883
3057
  */
2884
- getJSDoc(node) {
3058
+ getLeadingComment(node) {
2885
3059
  if (node.type === 'BlockStatement') {
2886
3060
  node = this.parent;
2887
3061
  }
@@ -2907,28 +3081,56 @@ class Asserter extends Stringifier {
2907
3081
  if (leadingComments && leadingComments.length) {
2908
3082
  const lastComment = leadingComments[leadingComments.length - 1];
2909
3083
  if (lastComment.type === "CommentBlock") {
2910
- if (lastComment.value.includes('@event')) {
2911
- return;
2912
- }
2913
- if (node.type === 'ClassMethod' && node.kind === 'set') {
2914
- const paramName = this.getNameOfParam(node.params[0]);
2915
- if (node.params.length !== 1) {
2916
- this.warn("getJSDoc> setters require exactly one argument");
2917
- }
2918
- const setterType = parseJSDocSetter(lastComment.value, this.expandType);
2919
- if (!setterType) {
2920
- return;
2921
- }
2922
- return {
2923
- [paramName]: setterType
2924
- };
2925
- }
2926
- if (lastComment.value.includes('@ignoreRTI')) {
2927
- return;
2928
- }
2929
- return parseJSDoc(lastComment.value, this.expandType);
3084
+ return lastComment.value;
3085
+ }
3086
+ }
3087
+ }
3088
+ /**
3089
+ * @param {Node} node - The Babel AST node.
3090
+ * @todo ESLint problem:
3091
+ * returns {import('./parseJSDoc.js').ParseJSDocReturnType} The return value of `parseJSDoc`
3092
+ * returns {Record<string, import('./parseJSDoc.js').ExpandTypeReturnType> | undefined} The
3093
+ * return value of `parseJSDoc`.
3094
+ * @returns {Record<string, any>|undefined} asd
3095
+ */
3096
+ getJSDoc(node) {
3097
+ const comment = this.getLeadingComment(node);
3098
+ if (!comment) {
3099
+ return;
3100
+ }
3101
+ if (comment.includes('@event')) {
3102
+ return;
3103
+ }
3104
+ if (comment.includes('@ignoreRTI')) {
3105
+ return;
3106
+ }
3107
+ // Need to do same resolving as in: this.getLeadingComment(node)
3108
+ if (node.type === 'BlockStatement') {
3109
+ node = this.parent;
3110
+ }
3111
+ if (node.type === 'ClassMethod' && node.kind === 'set') {
3112
+ const paramName = this.getNameOfParam(node.params[0]);
3113
+ if (node.params.length !== 1) {
3114
+ this.warn("getJSDoc> setters require exactly one argument");
3115
+ }
3116
+ const setterType = parseJSDocSetter(comment, this.expandType);
3117
+ if (!setterType) {
3118
+ return;
2930
3119
  }
3120
+ const _params = {
3121
+ [paramName]: setterType
3122
+ };
3123
+ return {
3124
+ templates: undefined,
3125
+ params: _params
3126
+ };
2931
3127
  }
3128
+ const templates = parseJSDocTemplates(comment);
3129
+ const params = parseJSDoc(comment, this.expandType);
3130
+ return {
3131
+ templates,
3132
+ params
3133
+ };
2932
3134
  }
2933
3135
  /**
2934
3136
  * Retrieves the name of a parameter from a Babel AST node.
@@ -2947,8 +3149,7 @@ class Asserter extends Stringifier {
2947
3149
  return param.left.name;
2948
3150
  }
2949
3151
  }
2950
- debugger;
2951
- this.warn("unable to extra name from param in specified way - may contain too much information");
3152
+ this.warn("Unable to retrieve name from param in specified way - may contain too much information.");
2952
3153
  return this.toSource(param);
2953
3154
  }
2954
3155
  statsReset() {
@@ -3067,6 +3268,17 @@ class Asserter extends Stringifier {
3067
3268
  stat.unchecked++;
3068
3269
  return '';
3069
3270
  }
3271
+ const {
3272
+ templates,
3273
+ params
3274
+ } = jsdoc;
3275
+ if (!params) {
3276
+ console.warn("This should never happen, please check your input code.", this.getLeadingComment(node), {
3277
+ jsdoc
3278
+ });
3279
+ stat.unchecked++;
3280
+ return '';
3281
+ }
3070
3282
  stat.checked++;
3071
3283
  const {
3072
3284
  spaces
@@ -3077,17 +3289,21 @@ class Asserter extends Stringifier {
3077
3289
  if (this.ignoreLocations.includes(loc)) {
3078
3290
  return '// IGNORE RTI TYPE VALIDATIONS, KNOWN ISSUES\n';
3079
3291
  }
3292
+ if (templates) {
3293
+ const tmp = JSON.stringify(templates, null, 2).replaceAll('\n', '\n' + spaces);
3294
+ out += `\n${spaces}const rtiTemplates = ${tmp};`;
3295
+ }
3080
3296
  //out += `${spaces}/*${spaces} node.type=${node.type}\n${spaces}
3081
3297
  // ${JSON.stringify(jsdoc)}\n${parent}\n${spaces}*/\n`;
3082
- for (let name in jsdoc) {
3083
- const type = jsdoc[name];
3298
+ for (let name in params) {
3299
+ const type = params[name];
3084
3300
  const hasParam = this.nodeHasParamName(node, name);
3085
3301
  if (!hasParam) {
3086
3302
  let testNode = node;
3087
3303
  if (node.type === 'BlockStatement') {
3088
3304
  testNode = this.parent;
3089
3305
  }
3090
- const paramIndex = Object.keys(jsdoc).findIndex(_ => _ === name);
3306
+ const paramIndex = Object.keys(params).findIndex(_ => _ === name);
3091
3307
  const param = testNode.params[paramIndex];
3092
3308
  if (param) {
3093
3309
  const isObjectPattern = param.type === 'ObjectPattern';
@@ -3117,7 +3333,11 @@ class Asserter extends Stringifier {
3117
3333
  continue;
3118
3334
  }
3119
3335
  const _t = JSON.stringify(type.elementType, null, 2).replaceAll('\n', '\n' + spaces);
3120
- out += `${spaces}if (!inspectType(${element.name}, ${_t}, '${_loc}', '${name}')) {\n`;
3336
+ if (templates) {
3337
+ out += `${spaces}if (!inspectTypeWithTemplates(${element.name}, ${_t}, '${_loc}', '${name}', rtiTemplates)) {\n`;
3338
+ } else {
3339
+ out += `${spaces}if (!inspectType(${element.name}, ${_t}, '${_loc}', '${name}')) {\n`;
3340
+ }
3121
3341
  out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
3122
3342
  }
3123
3343
  continue;
@@ -3139,7 +3359,11 @@ class Asserter extends Stringifier {
3139
3359
  continue;
3140
3360
  }
3141
3361
  const _t2 = JSON.stringify(subType, null, 2).replaceAll('\n', '\n' + spaces);
3142
- out += `${spaces}if (!inspectType(${keyName}, ${_t2}, '${_loc}', '${name}')) {\n`;
3362
+ if (templates) {
3363
+ out += `${spaces}if (!inspectTypeWithTemplates(${keyName}, ${_t2}, '${_loc}', '${name}', rtiTemplates)) {\n`;
3364
+ } else {
3365
+ out += `${spaces}if (!inspectType(${keyName}, ${_t2}, '${_loc}', '${name}')) {\n`;
3366
+ }
3143
3367
  out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
3144
3368
  }
3145
3369
  continue;
@@ -3172,7 +3396,11 @@ class Asserter extends Stringifier {
3172
3396
  out += '\n';
3173
3397
  first = false;
3174
3398
  }
3175
- out += `${spaces}if (${prevCheck}!inspectType(${name}, ${t}, '${_loc3}', '${name}')) {\n`;
3399
+ if (templates) {
3400
+ out += `${spaces}if (${prevCheck}!inspectTypeWithTemplates(${name}, ${t}, '${_loc3}', '${name}', rtiTemplates)) {\n`;
3401
+ } else {
3402
+ out += `${spaces}if (${prevCheck}!inspectType(${name}, ${t}, '${_loc3}', '${name}')) {\n`;
3403
+ }
3176
3404
  out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
3177
3405
  }
3178
3406
  return out;
@@ -3698,4 +3926,4 @@ function toSourceBabelTS(node) {
3698
3926
  }
3699
3927
  }
3700
3928
 
3701
- export { Asserter, Stringifier, addTypeChecks, ast2json, ast2jsonForComparison, code2ast2code, compareAST, expandType, expandTypeBabelTS, expandTypeDepFree, extractCurlyContent, extractNameAndOptionality, nodeIsFunction, parseJSDoc, parseJSDocSetter, parseJSDocTypedef, parseType, parseTypeBabelTS, requiredTypeofs, simplifyType, toSourceBabelTS, toSourceTS, trimEndSpaces };
3929
+ export { Asserter, Stringifier, addTypeChecks, ast2json, ast2jsonForComparison, code2ast2code, compareAST, expandType, expandTypeBabelTS, expandTypeDepFree, extractCurlyContent, extractNameAndOptionality, nodeIsFunction, parseJSDoc, parseJSDocSetter, parseJSDocTemplates, parseJSDocTypedef, parseType, parseTypeBabelTS, requiredTypeofs, simplifyType, toSourceBabelTS, toSourceTS, trimEndSpaces };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bin": "bin.js",
3
3
  "name": "@runtime-type-inspector/transpiler",
4
- "version": "3.2.6",
4
+ "version": "3.2.7",
5
5
  "description": "Validating JSDoc types at runtime for high-quality types - Trust is good, control is better.",
6
6
  "main": "index.cjs",
7
7
  "module": "index.mjs",
@@ -19,7 +19,7 @@
19
19
  "homepage": "https://github.com/kungfooman/RuntimeTypeInspector.js#readme",
20
20
  "dependencies": {
21
21
  "@babel/parser": "^7.23.0",
22
- "@runtime-type-inspector/runtime": "^3.2.6",
22
+ "@runtime-type-inspector/runtime": "^3.2.7",
23
23
  "typescript": "^5.1.6"
24
24
  },
25
25
  "files": [