@runtime-type-inspector/transpiler 3.3.7 → 4.0.1

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 +357 -29
  2. package/index.mjs +333 -30
  3. package/package.json +2 -2
package/index.cjs CHANGED
@@ -320,11 +320,14 @@
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 {ts.TypeNode|ts.Identifier} node - The TypeScript AST node to convert.
323
+ * @param {ts.TypeNode|ts.Identifier|ts.QualifiedName|undefined} 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
  */
327
327
  function toSourceTS(node) {
328
+ if (!node) {
329
+ return 'undefined';
330
+ }
328
331
  var typeArguments = node.typeArguments,
329
332
  typeName = node.typeName;
330
333
  var kind_ = ts.SyntaxKind[node.kind];
@@ -333,6 +336,7 @@
333
336
  ArrayType = _ts$SyntaxKind.ArrayType,
334
337
  BooleanKeyword = _ts$SyntaxKind.BooleanKeyword,
335
338
  FunctionType = _ts$SyntaxKind.FunctionType,
339
+ JSDocFunctionType = _ts$SyntaxKind.JSDocFunctionType,
336
340
  Identifier = _ts$SyntaxKind.Identifier,
337
341
  IntersectionType = _ts$SyntaxKind.IntersectionType,
338
342
  JSDocAllType = _ts$SyntaxKind.JSDocAllType,
@@ -371,7 +375,8 @@
371
375
  ConstructorType = _ts$SyntaxKind.ConstructorType,
372
376
  NamedTupleMember = _ts$SyntaxKind.NamedTupleMember,
373
377
  MappedType = _ts$SyntaxKind.MappedType,
374
- TypeParameter = _ts$SyntaxKind.TypeParameter;
378
+ TypeParameter = _ts$SyntaxKind.TypeParameter,
379
+ QualifiedName = _ts$SyntaxKind.QualifiedName;
375
380
  // console.log({typeArguments, typeName, kind_, node});
376
381
  switch (node.kind) {
377
382
  case BigIntKeyword:
@@ -410,23 +415,36 @@
410
415
  if (!ts.isConstructorTypeNode(node)) {
411
416
  throw Error("Impossible");
412
417
  }
413
- var _parameters = node.parameters.map(toSourceTS);
418
+ var parameters = node.parameters.map(toSourceTS);
414
419
  var _ret = toSourceTS(node.type);
415
420
  return {
416
421
  type: 'new',
417
- parameters: _parameters,
422
+ parameters: parameters,
418
423
  ret: _ret
419
424
  };
420
425
  }
421
426
  case FunctionType:
422
- if (!ts.isFunctionTypeNode(node)) {
423
- throw Error("Impossible");
427
+ {
428
+ if (!ts.isFunctionTypeNode(node)) {
429
+ throw Error("Impossible");
430
+ }
431
+ var _parameters = node.parameters.map(toSourceTS);
432
+ return {
433
+ type: 'function',
434
+ parameters: _parameters
435
+ };
436
+ }
437
+ case JSDocFunctionType:
438
+ {
439
+ if (!ts.isJSDocFunctionType(node)) {
440
+ throw Error("Impossible");
441
+ }
442
+ var _parameters2 = node.parameters.map(toSourceTS);
443
+ return {
444
+ type: 'function',
445
+ parameters: _parameters2
446
+ };
424
447
  }
425
- var parameters = node.parameters.map(toSourceTS);
426
- return {
427
- type: 'function',
428
- parameters: parameters
429
- };
430
448
  case IndexedAccessType:
431
449
  if (!ts.isIndexedAccessTypeNode(node)) {
432
450
  throw Error("Impossible");
@@ -721,6 +739,14 @@
721
739
  }
722
740
  // fall-through for parentheses
723
741
  return toSourceTS(node.type);
742
+ // toSourceTS(parseType("import('abc').x.y.z"))
743
+ case QualifiedName:
744
+ if (!ts.isQualifiedName(node)) {
745
+ throw Error("Impossible");
746
+ }
747
+ var l = toSourceTS(node.left);
748
+ var r = toSourceTS(node.right);
749
+ return "".concat(l, ".").concat(r);
724
750
  case ImportType:
725
751
  if (!ts.isImportTypeNode(node)) {
726
752
  throw Error("Impossible");
@@ -1235,6 +1261,16 @@
1235
1261
  stat.unchecked = 0;
1236
1262
  }
1237
1263
 
1264
+ /**
1265
+ * @example
1266
+ * capitalize("hello"); // Outputs: Hello
1267
+ * @param {string} _ - The input string.
1268
+ * @returns {string} The capitalized output string.
1269
+ */
1270
+ function capitalize(_) {
1271
+ return _[0].toUpperCase() + _.slice(1);
1272
+ }
1273
+
1238
1274
  /**
1239
1275
  * @example
1240
1276
  * trimEndSpaces('test \n '); // Returns: 'test \n'
@@ -1263,8 +1299,13 @@
1263
1299
  _defineProperty(this, "forceCurly", false);
1264
1300
  /** @type {Node[]} */
1265
1301
  _defineProperty(this, "parents", []);
1302
+ /**
1303
+ * If we encounter JSX elements, we add React's createElement as import.
1304
+ */
1305
+ _defineProperty(this, "addReactImport", false);
1266
1306
  _defineProperty(this, "lastCommentBlockIndex", -1);
1267
1307
  _defineProperty(this, "lastCommentLineIndex", -1);
1308
+ _defineProperty(this, "encounteredFragment", false);
1268
1309
  _defineProperty(this, "numSpaces", 0);
1269
1310
  }
1270
1311
  _createClass(Stringifier, [{
@@ -1519,6 +1560,21 @@
1519
1560
  }
1520
1561
  return out;
1521
1562
  }
1563
+ }, {
1564
+ key: "getHeader",
1565
+ value:
1566
+ /**
1567
+ * @returns {string} The header.
1568
+ */
1569
+ function getHeader() {
1570
+ if (!this.addReactImport) {
1571
+ return '';
1572
+ }
1573
+ if (this.encounteredFragment) {
1574
+ return "import {createElement, Fragment} from 'react';\n";
1575
+ }
1576
+ return "import {createElement} from 'react';\n";
1577
+ }
1522
1578
  }, {
1523
1579
  key: "parent",
1524
1580
  get: function get() {
@@ -1901,7 +1957,10 @@
1901
1957
  out += ' * ';
1902
1958
  }
1903
1959
  out += this.FunctionDeclarationParams(params);
1904
- out += ' => ';
1960
+ out += ' =>';
1961
+ if (body.type !== 'BlockStatement') {
1962
+ out += ' ';
1963
+ }
1905
1964
  out += this.toSource(body);
1906
1965
  return out;
1907
1966
  }
@@ -2535,6 +2594,219 @@
2535
2594
  // createMesh: createMesh$1\n
2536
2595
  return "".concat(spaces).concat(right, " as ").concat(left);
2537
2596
  }
2597
+ /**
2598
+ * @param {import("@babel/types").JSXAttribute} node - The Babel AST node.
2599
+ * @returns {string} Stringification of the node.
2600
+ */
2601
+ }, {
2602
+ key: "JSXAttribute",
2603
+ value: function JSXAttribute(node) {
2604
+ var name = node.name,
2605
+ value = node.value;
2606
+ // console.log("JSXAttribute", {name, value});
2607
+ var keySource = this.toSource(name);
2608
+ // E.g. <audio controls/>
2609
+ if (value === null) {
2610
+ return "".concat(this.spaces).concat(keySource, ": true");
2611
+ }
2612
+ var valSource = this.toSource(value);
2613
+ if (keySource === valSource) {
2614
+ return this.spaces + keySource;
2615
+ }
2616
+ return "".concat(this.spaces).concat(keySource, ": ").concat(valSource);
2617
+ }
2618
+ /**
2619
+ * @param {import("@babel/types").JSXElement} node - The Babel AST node.
2620
+ * @returns {string} Stringification of the node.
2621
+ */
2622
+ }, {
2623
+ key: "JSXElement",
2624
+ value: function JSXElement(node) {
2625
+ var _this4 = this;
2626
+ this.addReactImport = true;
2627
+ var openingElement = node.openingElement;
2628
+ node.closingElement;
2629
+ var children = node.children;
2630
+ // console.log("JSXElement", {openingElement, closingElement, children});
2631
+ if (openingElement.type !== 'JSXOpeningElement') {
2632
+ debugger;
2633
+ }
2634
+ var spaces = this.spaces;
2635
+ var name = openingElement.name,
2636
+ attributes = openingElement.attributes;
2637
+ var out = '';
2638
+ out += 'createElement(';
2639
+ this.numSpaces++;
2640
+ spaces = this.spaces;
2641
+ out += '\n';
2642
+ out += spaces;
2643
+ var sourceName = this.toSource(name);
2644
+ if (sourceName[0] === sourceName[0].toLowerCase()) {
2645
+ // If something like 'div', it has to become "'div'"
2646
+ sourceName = JSON.stringify(sourceName);
2647
+ }
2648
+ out += sourceName;
2649
+ out += ',';
2650
+ if (attributes.length) {
2651
+ out += '\n';
2652
+ out += spaces;
2653
+ out += '{\n';
2654
+ this.numSpaces++;
2655
+ out += attributes.map(function (attr) {
2656
+ return _this4.toSource(attr) + ',\n';
2657
+ }).join('');
2658
+ this.numSpaces--;
2659
+ out += spaces;
2660
+ out += '}';
2661
+ } else {
2662
+ out += '\n';
2663
+ out += spaces;
2664
+ out += 'null';
2665
+ }
2666
+ if (children.length) {
2667
+ out += ',\n';
2668
+ var _iterator2 = _createForOfIteratorHelper(children),
2669
+ _step2;
2670
+ try {
2671
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2672
+ var child = _step2.value;
2673
+ var source = this.toSource(child);
2674
+ if (!source.length) {
2675
+ continue;
2676
+ }
2677
+ out += spaces;
2678
+ out += source;
2679
+ out += ',\n';
2680
+ }
2681
+ } catch (err) {
2682
+ _iterator2.e(err);
2683
+ } finally {
2684
+ _iterator2.f();
2685
+ }
2686
+ } else {
2687
+ out += '\n';
2688
+ }
2689
+ this.numSpaces--;
2690
+ out += this.spaces;
2691
+ out += ')';
2692
+ return out;
2693
+ }
2694
+ /**
2695
+ * @param {import("@babel/types").JSXFragment} node - The Babel AST node.
2696
+ * @returns {string} Stringification of the node.
2697
+ */
2698
+ }, {
2699
+ key: "JSXFragment",
2700
+ value: function JSXFragment(node) {
2701
+ var children = node.children;
2702
+ this.encounteredFragment = true;
2703
+ // console.log('JSXFragment', {openingFragment, closingFragment, children});
2704
+ var spaces = this.spaces;
2705
+ var out = 'createElement(\n';
2706
+ this.numSpaces++;
2707
+ spaces = this.spaces;
2708
+ out += spaces;
2709
+ out += 'Fragment,\n';
2710
+ out += spaces;
2711
+ out += 'null';
2712
+ // Same code for children as in JSXElement
2713
+ if (children.length) {
2714
+ out += ',\n';
2715
+ var _iterator3 = _createForOfIteratorHelper(children),
2716
+ _step3;
2717
+ try {
2718
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2719
+ var child = _step3.value;
2720
+ var source = this.toSource(child);
2721
+ if (!source.length) {
2722
+ continue;
2723
+ }
2724
+ out += spaces;
2725
+ out += source;
2726
+ out += ',\n';
2727
+ }
2728
+ } catch (err) {
2729
+ _iterator3.e(err);
2730
+ } finally {
2731
+ _iterator3.f();
2732
+ }
2733
+ } else {
2734
+ out += '\n';
2735
+ }
2736
+ this.numSpaces--;
2737
+ out += this.spaces;
2738
+ out += ')';
2739
+ return out;
2740
+ }
2741
+ /**
2742
+ * @param {import("@babel/types").JSXIdentifier} node - The Babel AST node.
2743
+ * @returns {string} Stringification of the node.
2744
+ */
2745
+ }, {
2746
+ key: "JSXIdentifier",
2747
+ value: function JSXIdentifier(node) {
2748
+ var name = node.name;
2749
+ // console.log('JSXIdentifier', {name});
2750
+ // https://github.com/kungfooman/RuntimeTypeInspector.js/issues/201
2751
+ // https://stackoverflow.com/questions/38998987/using-aria-attributes-on-elements-in-react
2752
+ if (name.includes('-')) {
2753
+ return JSON.stringify(name);
2754
+ }
2755
+ return name;
2756
+ }
2757
+ /**
2758
+ * @param {import("@babel/types").JSXNamespacedName} node - The Babel AST node.
2759
+ * @returns {string} Stringification of the node.
2760
+ */
2761
+ }, {
2762
+ key: "JSXNamespacedName",
2763
+ value: function JSXNamespacedName(node) {
2764
+ var namespace = node.namespace,
2765
+ name = node.name;
2766
+ var a = this.toSource(namespace);
2767
+ var b = capitalize(this.toSource(name));
2768
+ return a + b;
2769
+ }
2770
+ /**
2771
+ * @param {import("@babel/types").JSXExpressionContainer} node - The Babel AST node.
2772
+ * @returns {string} Stringification of the node.
2773
+ */
2774
+ }, {
2775
+ key: "JSXExpressionContainer",
2776
+ value: function JSXExpressionContainer(node) {
2777
+ var expression = node.expression;
2778
+ // console.log('JSXExpressionContainer', {expression});
2779
+ return this.toSource(expression);
2780
+ }
2781
+ /**
2782
+ * @param {import("@babel/types").JSXMemberExpression} node - The Babel AST node.
2783
+ * @returns {string} Stringification of the node.
2784
+ */
2785
+ }, {
2786
+ key: "JSXMemberExpression",
2787
+ value: function JSXMemberExpression(node) {
2788
+ var object = node.object,
2789
+ property = node.property;
2790
+ // console.log('JSXMemberExpression', 'object', object, 'property', property);
2791
+ var o = this.toSource(object);
2792
+ var p = this.toSource(property);
2793
+ return "".concat(o, ".").concat(p);
2794
+ }
2795
+ /**
2796
+ * @param {import("@babel/types").JSXText} node - The Babel AST node.
2797
+ * @returns {string} Stringification of the node.
2798
+ */
2799
+ }, {
2800
+ key: "JSXText",
2801
+ value: function JSXText(node) {
2802
+ node.extra;
2803
+ var value = node.value;
2804
+ // console.log('JSXText', {extra, value});
2805
+ if (!value.trim().length) {
2806
+ return '';
2807
+ }
2808
+ return JSON.stringify(value);
2809
+ }
2538
2810
  /**
2539
2811
  * @param {import("@babel/types").Super} node - The Babel AST node.
2540
2812
  * @returns {string} Stringification of the node.
@@ -2927,6 +3199,22 @@
2927
3199
  value: function Import(node) {
2928
3200
  return 'import';
2929
3201
  }
3202
+ /**
3203
+ * @param {import("@babel/types").ImportExpression} node - The Babel AST node.
3204
+ * @returns {string} Stringification of the node.
3205
+ */
3206
+ }, {
3207
+ key: "ImportExpression",
3208
+ value: function ImportExpression(node) {
3209
+ var source = node.source,
3210
+ options = node.options;
3211
+ var out = "import(" + this.toSource(source);
3212
+ if (options) {
3213
+ out += ', ' + this.toSource(options);
3214
+ }
3215
+ out += ')';
3216
+ return out;
3217
+ }
2930
3218
  /**
2931
3219
  * @param {import("@babel/types").ImportDeclaration} node - The Babel AST node.
2932
3220
  * @returns {string} Stringification of the node.
@@ -2939,15 +3227,39 @@
2939
3227
  // ImportNamespaceSpecifier: import * as a from "b";
2940
3228
  // ImportDefaultSpecifier : import a from "b";
2941
3229
  // ImportSpecifier : import { a } from "b";
2942
- var a = this.mapToSource(specifiers).join(', ');
2943
- var b = this.toSource(source);
3230
+ var from = this.toSource(source);
2944
3231
  if (specifiers.length === 0) {
2945
- return "import ".concat(b);
3232
+ return "import ".concat(from);
2946
3233
  }
2947
- if (specifiers[0].type !== 'ImportSpecifier') {
2948
- return "import ".concat(a, " from ").concat(b, ";");
3234
+ var out = 'import ';
3235
+ var n = specifiers.length;
3236
+ var inImportSpecifier = false;
3237
+ var i = 0;
3238
+ var _iterator4 = _createForOfIteratorHelper(specifiers),
3239
+ _step4;
3240
+ try {
3241
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
3242
+ var specifier = _step4.value;
3243
+ if (specifier.type === 'ImportSpecifier' && !inImportSpecifier) {
3244
+ out += "{";
3245
+ inImportSpecifier = true;
3246
+ }
3247
+ out += this.toSource(specifier);
3248
+ if (i !== n - 1) {
3249
+ out += ', ';
3250
+ }
3251
+ i++;
3252
+ }
3253
+ } catch (err) {
3254
+ _iterator4.e(err);
3255
+ } finally {
3256
+ _iterator4.f();
2949
3257
  }
2950
- return "import {".concat(a, "} from ").concat(b, ";");
3258
+ if (inImportSpecifier) {
3259
+ out += '}';
3260
+ }
3261
+ out += " from ".concat(from, ";");
3262
+ return out;
2951
3263
  }
2952
3264
  /**
2953
3265
  * @param {import("@babel/types").ImportSpecifier} node - The Babel AST node.
@@ -3216,7 +3528,11 @@
3216
3528
  } else {
3217
3529
  out += ' {\n';
3218
3530
  out += this.generateTypeChecks(node);
3531
+ var spaces = this.spaces;
3532
+ this.numSpaces++;
3219
3533
  out += this.spaces + 'return ' + this.toSource(body) + ';\n';
3534
+ this.numSpaces--;
3535
+ out += spaces;
3220
3536
  out += '}';
3221
3537
  }
3222
3538
  return out;
@@ -3262,6 +3578,7 @@
3262
3578
  * @todo
3263
3579
  * Once we went over every node, we can see if we really require registerTypef, registerClass etc.
3264
3580
  * @returns {string} The import declaration header for importing RTI.
3581
+ * @override
3265
3582
  */
3266
3583
  }, {
3267
3584
  key: "getHeader",
@@ -3269,7 +3586,8 @@
3269
3586
  if (!this.addHeader) {
3270
3587
  return '';
3271
3588
  }
3272
- var header = "import {inspectType, inspectTypeWithTemplates, youCanAddABreakpointHere, registerVariable";
3589
+ var header = _get(_getPrototypeOf(Asserter.prototype), "getHeader", this).call(this);
3590
+ header += "import {inspectType, inspectTypeWithTemplates, youCanAddABreakpointHere, registerVariable";
3273
3591
  if (this.validateDivision) {
3274
3592
  header += ", validateDivision";
3275
3593
  }
@@ -4001,6 +4319,17 @@
4001
4319
  return Asserter;
4002
4320
  }(Stringifier);
4003
4321
 
4322
+ /**
4323
+ * We use the same options in different places, so it's nice to keep them in sync without
4324
+ * changing them in multiple locations.
4325
+ * @type {import('@babel/parser').ParserOptions}
4326
+ */
4327
+ var parserOptions = {
4328
+ sourceType: 'module',
4329
+ createParenthesizedExpressions: true,
4330
+ plugins: ['jsx']
4331
+ };
4332
+
4004
4333
  /**
4005
4334
  * Simple facade which does all the processing. Processes the input
4006
4335
  * source string, adding runtime type checks based on JSDoc comments.
@@ -4020,11 +4349,10 @@
4020
4349
  function addTypeChecks(src, options) {
4021
4350
  try {
4022
4351
  var asserter = new Asserter(options);
4023
- var ast = parser.parse(src, {
4024
- sourceType: 'module',
4025
- createParenthesizedExpressions: true
4026
- });
4027
- var out = asserter.getHeader() + asserter.toSource(ast);
4352
+ var ast = parser.parse(src, parserOptions);
4353
+ // Must be called before getHeader since it's keeping an eye open for JSX elements.
4354
+ var source = asserter.toSource(ast);
4355
+ var out = asserter.getHeader() + source;
4028
4356
  return out;
4029
4357
  } catch (e) {
4030
4358
  console.error(e);
@@ -4072,15 +4400,13 @@
4072
4400
  * @returns {string | undefined} The new and once parsed and stringified code.
4073
4401
  */
4074
4402
  function code2ast2code(code) {
4075
- var stringifier = new Stringifier();
4076
- var ast = parser.parse(code, {
4077
- sourceType: 'module'
4078
- });
4403
+ var ast = parser.parse(code, parserOptions);
4079
4404
  if (!ast) {
4080
4405
  return;
4081
4406
  }
4407
+ var stringifier = new Stringifier();
4082
4408
  var out = stringifier.toSource(ast);
4083
- return out;
4409
+ return stringifier.getHeader() + out;
4084
4410
  }
4085
4411
 
4086
4412
  /**
@@ -4347,6 +4673,7 @@
4347
4673
  exports.addTypeChecks = addTypeChecks;
4348
4674
  exports.ast2json = ast2json;
4349
4675
  exports.ast2jsonForComparison = ast2jsonForComparison;
4676
+ exports.capitalize = capitalize;
4350
4677
  exports.code2ast2code = code2ast2code;
4351
4678
  exports.compareAST = compareAST;
4352
4679
  exports.expandType = expandType;
@@ -4361,6 +4688,7 @@
4361
4688
  exports.parseJSDocTypedef = parseJSDocTypedef;
4362
4689
  exports.parseType = parseType;
4363
4690
  exports.parseTypeBabelTS = parseTypeBabelTS;
4691
+ exports.parserOptions = parserOptions;
4364
4692
  exports.requiredTypeofs = requiredTypeofs;
4365
4693
  exports.simplifyType = simplifyType;
4366
4694
  exports.toSourceBabelTS = toSourceBabelTS;
package/index.mjs CHANGED
@@ -63,11 +63,14 @@ 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 {ts.TypeNode|ts.Identifier} node - The TypeScript AST node to convert.
66
+ * @param {ts.TypeNode|ts.Identifier|ts.QualifiedName|undefined} 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
  */
70
70
  function toSourceTS(node) {
71
+ if (!node) {
72
+ return 'undefined';
73
+ }
71
74
  const {
72
75
  typeArguments,
73
76
  typeName
@@ -82,6 +85,8 @@ function toSourceTS(node) {
82
85
  // parseType("boolean" ).kind === ts.SyntaxKind.BooleanKeyword
83
86
  FunctionType,
84
87
  // parseType("() => void" ).kind === ts.SyntaxKind.FunctionType
88
+ JSDocFunctionType,
89
+ // parseType("function (number, number): number").kind === ts.SyntaxKind.JSDocFunctionType
85
90
  Identifier,
86
91
  // parseType("{a: 1, b: 2}" ).members[0].name.kind === ts.SyntaxKind.Identifier
87
92
  IntersectionType,
@@ -158,7 +163,9 @@ function toSourceTS(node) {
158
163
  // parseType('[a: 1]' ).elements[0].kind === ts.SyntaxKind.NamedTupleMember
159
164
  MappedType,
160
165
  // parseType('{[K in TaskType]: 123}' ).kind === ts.SyntaxKind.MappedType
161
- TypeParameter // parseType('{[K in TaskType]: 123}' ).typeParameter.kind === ts.SyntaxKind.TypeParameter
166
+ TypeParameter,
167
+ // parseType('{[K in TaskType]: 123}' ).typeParameter.kind === ts.SyntaxKind.TypeParameter
168
+ QualifiedName // parseType("import('abc').x.y" ).qualifier.kind === ts.SyntaxKind.QualifiedName
162
169
  } = ts.SyntaxKind;
163
170
  // console.log({typeArguments, typeName, kind_, node});
164
171
  switch (node.kind) {
@@ -198,23 +205,36 @@ function toSourceTS(node) {
198
205
  if (!ts.isConstructorTypeNode(node)) {
199
206
  throw Error("Impossible");
200
207
  }
201
- const _parameters = node.parameters.map(toSourceTS);
208
+ const parameters = node.parameters.map(toSourceTS);
202
209
  const _ret = toSourceTS(node.type);
203
210
  return {
204
211
  type: 'new',
205
- parameters: _parameters,
212
+ parameters,
206
213
  ret: _ret
207
214
  };
208
215
  }
209
216
  case FunctionType:
210
- if (!ts.isFunctionTypeNode(node)) {
211
- throw Error("Impossible");
217
+ {
218
+ if (!ts.isFunctionTypeNode(node)) {
219
+ throw Error("Impossible");
220
+ }
221
+ const parameters = node.parameters.map(toSourceTS);
222
+ return {
223
+ type: 'function',
224
+ parameters
225
+ };
226
+ }
227
+ case JSDocFunctionType:
228
+ {
229
+ if (!ts.isJSDocFunctionType(node)) {
230
+ throw Error("Impossible");
231
+ }
232
+ const parameters = node.parameters.map(toSourceTS);
233
+ return {
234
+ type: 'function',
235
+ parameters
236
+ };
212
237
  }
213
- const parameters = node.parameters.map(toSourceTS);
214
- return {
215
- type: 'function',
216
- parameters
217
- };
218
238
  case IndexedAccessType:
219
239
  if (!ts.isIndexedAccessTypeNode(node)) {
220
240
  throw Error("Impossible");
@@ -509,6 +529,14 @@ function toSourceTS(node) {
509
529
  }
510
530
  // fall-through for parentheses
511
531
  return toSourceTS(node.type);
532
+ // toSourceTS(parseType("import('abc').x.y.z"))
533
+ case QualifiedName:
534
+ if (!ts.isQualifiedName(node)) {
535
+ throw Error("Impossible");
536
+ }
537
+ const l = toSourceTS(node.left);
538
+ const r = toSourceTS(node.right);
539
+ return `${l}.${r}`;
512
540
  case ImportType:
513
541
  if (!ts.isImportTypeNode(node)) {
514
542
  throw Error("Impossible");
@@ -994,6 +1022,16 @@ function statReset(stat) {
994
1022
  stat.unchecked = 0;
995
1023
  }
996
1024
 
1025
+ /**
1026
+ * @example
1027
+ * capitalize("hello"); // Outputs: Hello
1028
+ * @param {string} _ - The input string.
1029
+ * @returns {string} The capitalized output string.
1030
+ */
1031
+ function capitalize(_) {
1032
+ return _[0].toUpperCase() + _.slice(1);
1033
+ }
1034
+
997
1035
  /**
998
1036
  * @example
999
1037
  * trimEndSpaces('test \n '); // Returns: 'test \n'
@@ -1021,8 +1059,13 @@ class Stringifier {
1021
1059
  this.forceCurly = false;
1022
1060
  /** @type {Node[]} */
1023
1061
  this.parents = [];
1062
+ /**
1063
+ * If we encounter JSX elements, we add React's createElement as import.
1064
+ */
1065
+ this.addReactImport = false;
1024
1066
  this.lastCommentBlockIndex = -1;
1025
1067
  this.lastCommentLineIndex = -1;
1068
+ this.encounteredFragment = false;
1026
1069
  this.numSpaces = 0;
1027
1070
  }
1028
1071
  /**
@@ -1258,6 +1301,18 @@ class Stringifier {
1258
1301
  }
1259
1302
  return out;
1260
1303
  }
1304
+ /**
1305
+ * @returns {string} The header.
1306
+ */
1307
+ getHeader() {
1308
+ if (!this.addReactImport) {
1309
+ return '';
1310
+ }
1311
+ if (this.encounteredFragment) {
1312
+ return "import {createElement, Fragment} from 'react';\n";
1313
+ }
1314
+ return "import {createElement} from 'react';\n";
1315
+ }
1261
1316
  get parent() {
1262
1317
  return this.parents[this.parents.length - 2];
1263
1318
  }
@@ -1618,7 +1673,10 @@ class Stringifier {
1618
1673
  out += ' * ';
1619
1674
  }
1620
1675
  out += this.FunctionDeclarationParams(params);
1621
- out += ' => ';
1676
+ out += ' =>';
1677
+ if (body.type !== 'BlockStatement') {
1678
+ out += ' ';
1679
+ }
1622
1680
  out += this.toSource(body);
1623
1681
  return out;
1624
1682
  }
@@ -2249,6 +2307,204 @@ class Stringifier {
2249
2307
  // createMesh: createMesh$1\n
2250
2308
  return `${spaces}${right} as ${left}`;
2251
2309
  }
2310
+ /**
2311
+ * @param {import("@babel/types").JSXAttribute} node - The Babel AST node.
2312
+ * @returns {string} Stringification of the node.
2313
+ */
2314
+ JSXAttribute(node) {
2315
+ const {
2316
+ name,
2317
+ value
2318
+ } = node;
2319
+ // console.log("JSXAttribute", {name, value});
2320
+ const keySource = this.toSource(name);
2321
+ // E.g. <audio controls/>
2322
+ if (value === null) {
2323
+ return `${this.spaces}${keySource}: true`;
2324
+ }
2325
+ const valSource = this.toSource(value);
2326
+ if (keySource === valSource) {
2327
+ return this.spaces + keySource;
2328
+ }
2329
+ return `${this.spaces}${keySource}: ${valSource}`;
2330
+ }
2331
+ /**
2332
+ * @param {import("@babel/types").JSXElement} node - The Babel AST node.
2333
+ * @returns {string} Stringification of the node.
2334
+ */
2335
+ JSXElement(node) {
2336
+ this.addReactImport = true;
2337
+ const {
2338
+ openingElement,
2339
+ closingElement,
2340
+ children
2341
+ } = node;
2342
+ // console.log("JSXElement", {openingElement, closingElement, children});
2343
+ if (openingElement.type !== 'JSXOpeningElement') {
2344
+ debugger;
2345
+ }
2346
+ let {
2347
+ spaces
2348
+ } = this;
2349
+ const {
2350
+ name,
2351
+ attributes
2352
+ } = openingElement;
2353
+ let out = '';
2354
+ out += 'createElement(';
2355
+ this.numSpaces++;
2356
+ spaces = this.spaces;
2357
+ out += '\n';
2358
+ out += spaces;
2359
+ let sourceName = this.toSource(name);
2360
+ if (sourceName[0] === sourceName[0].toLowerCase()) {
2361
+ // If something like 'div', it has to become "'div'"
2362
+ sourceName = JSON.stringify(sourceName);
2363
+ }
2364
+ out += sourceName;
2365
+ out += ',';
2366
+ if (attributes.length) {
2367
+ out += '\n';
2368
+ out += spaces;
2369
+ out += '{\n';
2370
+ this.numSpaces++;
2371
+ out += attributes.map(attr => this.toSource(attr) + ',\n').join('');
2372
+ this.numSpaces--;
2373
+ out += spaces;
2374
+ out += '}';
2375
+ } else {
2376
+ out += '\n';
2377
+ out += spaces;
2378
+ out += 'null';
2379
+ }
2380
+ if (children.length) {
2381
+ out += ',\n';
2382
+ for (const child of children) {
2383
+ const source = this.toSource(child);
2384
+ if (!source.length) {
2385
+ continue;
2386
+ }
2387
+ out += spaces;
2388
+ out += source;
2389
+ out += ',\n';
2390
+ }
2391
+ } else {
2392
+ out += '\n';
2393
+ }
2394
+ this.numSpaces--;
2395
+ out += this.spaces;
2396
+ out += ')';
2397
+ return out;
2398
+ }
2399
+ /**
2400
+ * @param {import("@babel/types").JSXFragment} node - The Babel AST node.
2401
+ * @returns {string} Stringification of the node.
2402
+ */
2403
+ JSXFragment(node) {
2404
+ const {
2405
+ /*openingFragment, closingFragment,*/children
2406
+ } = node;
2407
+ this.encounteredFragment = true;
2408
+ // console.log('JSXFragment', {openingFragment, closingFragment, children});
2409
+ let {
2410
+ spaces
2411
+ } = this;
2412
+ let out = 'createElement(\n';
2413
+ this.numSpaces++;
2414
+ spaces = this.spaces;
2415
+ out += spaces;
2416
+ out += 'Fragment,\n';
2417
+ out += spaces;
2418
+ out += 'null';
2419
+ // Same code for children as in JSXElement
2420
+ if (children.length) {
2421
+ out += ',\n';
2422
+ for (const child of children) {
2423
+ const source = this.toSource(child);
2424
+ if (!source.length) {
2425
+ continue;
2426
+ }
2427
+ out += spaces;
2428
+ out += source;
2429
+ out += ',\n';
2430
+ }
2431
+ } else {
2432
+ out += '\n';
2433
+ }
2434
+ this.numSpaces--;
2435
+ out += this.spaces;
2436
+ out += ')';
2437
+ return out;
2438
+ }
2439
+ /**
2440
+ * @param {import("@babel/types").JSXIdentifier} node - The Babel AST node.
2441
+ * @returns {string} Stringification of the node.
2442
+ */
2443
+ JSXIdentifier(node) {
2444
+ const {
2445
+ name
2446
+ } = node;
2447
+ // console.log('JSXIdentifier', {name});
2448
+ // https://github.com/kungfooman/RuntimeTypeInspector.js/issues/201
2449
+ // https://stackoverflow.com/questions/38998987/using-aria-attributes-on-elements-in-react
2450
+ if (name.includes('-')) {
2451
+ return JSON.stringify(name);
2452
+ }
2453
+ return name;
2454
+ }
2455
+ /**
2456
+ * @param {import("@babel/types").JSXNamespacedName} node - The Babel AST node.
2457
+ * @returns {string} Stringification of the node.
2458
+ */
2459
+ JSXNamespacedName(node) {
2460
+ const {
2461
+ namespace,
2462
+ name
2463
+ } = node;
2464
+ const a = this.toSource(namespace);
2465
+ const b = capitalize(this.toSource(name));
2466
+ return a + b;
2467
+ }
2468
+ /**
2469
+ * @param {import("@babel/types").JSXExpressionContainer} node - The Babel AST node.
2470
+ * @returns {string} Stringification of the node.
2471
+ */
2472
+ JSXExpressionContainer(node) {
2473
+ const {
2474
+ expression
2475
+ } = node;
2476
+ // console.log('JSXExpressionContainer', {expression});
2477
+ return this.toSource(expression);
2478
+ }
2479
+ /**
2480
+ * @param {import("@babel/types").JSXMemberExpression} node - The Babel AST node.
2481
+ * @returns {string} Stringification of the node.
2482
+ */
2483
+ JSXMemberExpression(node) {
2484
+ const {
2485
+ object,
2486
+ property
2487
+ } = node;
2488
+ // console.log('JSXMemberExpression', 'object', object, 'property', property);
2489
+ const o = this.toSource(object);
2490
+ const p = this.toSource(property);
2491
+ return `${o}.${p}`;
2492
+ }
2493
+ /**
2494
+ * @param {import("@babel/types").JSXText} node - The Babel AST node.
2495
+ * @returns {string} Stringification of the node.
2496
+ */
2497
+ JSXText(node) {
2498
+ const {
2499
+ extra,
2500
+ value
2501
+ } = node;
2502
+ // console.log('JSXText', {extra, value});
2503
+ if (!value.trim().length) {
2504
+ return '';
2505
+ }
2506
+ return JSON.stringify(value);
2507
+ }
2252
2508
  /**
2253
2509
  * @param {import("@babel/types").Super} node - The Babel AST node.
2254
2510
  * @returns {string} Stringification of the node.
@@ -2647,6 +2903,22 @@ class Stringifier {
2647
2903
  Import(node) {
2648
2904
  return 'import';
2649
2905
  }
2906
+ /**
2907
+ * @param {import("@babel/types").ImportExpression} node - The Babel AST node.
2908
+ * @returns {string} Stringification of the node.
2909
+ */
2910
+ ImportExpression(node) {
2911
+ const {
2912
+ source,
2913
+ options
2914
+ } = node;
2915
+ let out = "import(" + this.toSource(source);
2916
+ if (options) {
2917
+ out += ', ' + this.toSource(options);
2918
+ }
2919
+ out += ')';
2920
+ return out;
2921
+ }
2650
2922
  /**
2651
2923
  * @param {import("@babel/types").ImportDeclaration} node - The Babel AST node.
2652
2924
  * @returns {string} Stringification of the node.
@@ -2659,15 +2931,30 @@ class Stringifier {
2659
2931
  // ImportNamespaceSpecifier: import * as a from "b";
2660
2932
  // ImportDefaultSpecifier : import a from "b";
2661
2933
  // ImportSpecifier : import { a } from "b";
2662
- const a = this.mapToSource(specifiers).join(', ');
2663
- const b = this.toSource(source);
2934
+ const from = this.toSource(source);
2664
2935
  if (specifiers.length === 0) {
2665
- return `import ${b}`;
2936
+ return `import ${from}`;
2937
+ }
2938
+ let out = 'import ';
2939
+ const n = specifiers.length;
2940
+ let inImportSpecifier = false;
2941
+ let i = 0;
2942
+ for (const specifier of specifiers) {
2943
+ if (specifier.type === 'ImportSpecifier' && !inImportSpecifier) {
2944
+ out += "{";
2945
+ inImportSpecifier = true;
2946
+ }
2947
+ out += this.toSource(specifier);
2948
+ if (i !== n - 1) {
2949
+ out += ', ';
2950
+ }
2951
+ i++;
2666
2952
  }
2667
- if (specifiers[0].type !== 'ImportSpecifier') {
2668
- return `import ${a} from ${b};`;
2953
+ if (inImportSpecifier) {
2954
+ out += '}';
2669
2955
  }
2670
- return `import {${a}} from ${b};`;
2956
+ out += ` from ${from};`;
2957
+ return out;
2671
2958
  }
2672
2959
  /**
2673
2960
  * @param {import("@babel/types").ImportSpecifier} node - The Babel AST node.
@@ -2917,7 +3204,13 @@ class Asserter extends Stringifier {
2917
3204
  } else {
2918
3205
  out += ' {\n';
2919
3206
  out += this.generateTypeChecks(node);
3207
+ const {
3208
+ spaces
3209
+ } = this;
3210
+ this.numSpaces++;
2920
3211
  out += this.spaces + 'return ' + this.toSource(body) + ';\n';
3212
+ this.numSpaces--;
3213
+ out += spaces;
2921
3214
  out += '}';
2922
3215
  }
2923
3216
  return out;
@@ -2959,12 +3252,14 @@ class Asserter extends Stringifier {
2959
3252
  * @todo
2960
3253
  * Once we went over every node, we can see if we really require registerTypef, registerClass etc.
2961
3254
  * @returns {string} The import declaration header for importing RTI.
3255
+ * @override
2962
3256
  */
2963
3257
  getHeader() {
2964
3258
  if (!this.addHeader) {
2965
3259
  return '';
2966
3260
  }
2967
- let header = "import {inspectType, inspectTypeWithTemplates, youCanAddABreakpointHere, registerVariable";
3261
+ let header = super.getHeader();
3262
+ header += "import {inspectType, inspectTypeWithTemplates, youCanAddABreakpointHere, registerVariable";
2968
3263
  if (this.validateDivision) {
2969
3264
  header += ", validateDivision";
2970
3265
  }
@@ -3631,6 +3926,17 @@ class Asserter extends Stringifier {
3631
3926
  }
3632
3927
  }
3633
3928
 
3929
+ /**
3930
+ * We use the same options in different places, so it's nice to keep them in sync without
3931
+ * changing them in multiple locations.
3932
+ * @type {import('@babel/parser').ParserOptions}
3933
+ */
3934
+ const parserOptions = {
3935
+ sourceType: 'module',
3936
+ createParenthesizedExpressions: true,
3937
+ plugins: ['jsx']
3938
+ };
3939
+
3634
3940
  /**
3635
3941
  * Simple facade which does all the processing. Processes the input
3636
3942
  * source string, adding runtime type checks based on JSDoc comments.
@@ -3650,11 +3956,10 @@ class Asserter extends Stringifier {
3650
3956
  function addTypeChecks(src, options) {
3651
3957
  try {
3652
3958
  const asserter = new Asserter(options);
3653
- const ast = parse(src, {
3654
- sourceType: 'module',
3655
- createParenthesizedExpressions: true
3656
- });
3657
- const out = asserter.getHeader() + asserter.toSource(ast);
3959
+ const ast = parse(src, parserOptions);
3960
+ // Must be called before getHeader since it's keeping an eye open for JSX elements.
3961
+ const source = asserter.toSource(ast);
3962
+ const out = asserter.getHeader() + source;
3658
3963
  return out;
3659
3964
  } catch (e) {
3660
3965
  console.error(e);
@@ -3702,15 +4007,13 @@ function ast2jsonForComparison(ast) {
3702
4007
  * @returns {string | undefined} The new and once parsed and stringified code.
3703
4008
  */
3704
4009
  function code2ast2code(code) {
3705
- const stringifier = new Stringifier();
3706
- const ast = parse(code, {
3707
- sourceType: 'module'
3708
- });
4010
+ const ast = parse(code, parserOptions);
3709
4011
  if (!ast) {
3710
4012
  return;
3711
4013
  }
4014
+ const stringifier = new Stringifier();
3712
4015
  const out = stringifier.toSource(ast);
3713
- return out;
4016
+ return stringifier.getHeader() + out;
3714
4017
  }
3715
4018
 
3716
4019
  /**
@@ -3972,4 +4275,4 @@ function toSourceBabelTS(node) {
3972
4275
  }
3973
4276
  }
3974
4277
 
3975
- 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 };
4278
+ export { Asserter, Stringifier, addTypeChecks, ast2json, ast2jsonForComparison, capitalize, code2ast2code, compareAST, expandType, expandTypeBabelTS, expandTypeDepFree, extractCurlyContent, extractNameAndOptionality, nodeIsFunction, parseJSDoc, parseJSDocSetter, parseJSDocTemplates, parseJSDocTypedef, parseType, parseTypeBabelTS, parserOptions, 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.3.7",
4
+ "version": "4.0.1",
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.3.7",
22
+ "@runtime-type-inspector/runtime": "^4.0.1",
23
23
  "typescript": "^5.1.6"
24
24
  },
25
25
  "files": [