@runtime-type-inspector/transpiler 5.0.3 → 5.0.5

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 +173 -21
  2. package/index.mjs +154 -19
  3. package/package.json +2 -2
package/index.cjs CHANGED
@@ -449,9 +449,9 @@
449
449
  TypeQuery = _ts$SyntaxKind.TypeQuery,
450
450
  TypeOperator = _ts$SyntaxKind.TypeOperator,
451
451
  KeyOfKeyword = _ts$SyntaxKind.KeyOfKeyword,
452
- ReadonlyKeyword = _ts$SyntaxKind.ReadonlyKeyword;
453
- _ts$SyntaxKind.UniqueKeyword;
454
- var ConstructorType = _ts$SyntaxKind.ConstructorType,
452
+ ReadonlyKeyword = _ts$SyntaxKind.ReadonlyKeyword,
453
+ UniqueKeyword = _ts$SyntaxKind.UniqueKeyword,
454
+ ConstructorType = _ts$SyntaxKind.ConstructorType,
455
455
  NamedTupleMember = _ts$SyntaxKind.NamedTupleMember,
456
456
  MappedType = _ts$SyntaxKind.MappedType,
457
457
  MinusToken = _ts$SyntaxKind.MinusToken,
@@ -639,6 +639,11 @@
639
639
  // readonly erased at runtime, same shape as the inner type.
640
640
  return toSourceTS(node.type);
641
641
  }
642
+ if (node.operator === UniqueKeyword) {
643
+ // `unique symbol` is a symbol at runtime; the uniqueness refers to
644
+ // a specific declaration which we cannot track here.
645
+ return toSourceTS(node.type);
646
+ }
642
647
  console.warn("unimplemented TypeOperator", node);
643
648
  return 'any';
644
649
  case TypeReference:
@@ -1370,7 +1375,7 @@
1370
1375
  return expandTypeDepFree(type.slice(9).trim());
1371
1376
  }
1372
1377
  if (type === 'unique symbol') {
1373
- return 'any';
1378
+ return 'symbol';
1374
1379
  }
1375
1380
  // Conditionals before nullable: `A extends B ? C : D?` must keep the
1376
1381
  // nullable on the false branch, not lift it over the whole condition.
@@ -2634,6 +2639,21 @@
2634
2639
  var matches = _toConsumableArray(src.matchAll(regexTemplateTyped));
2635
2640
  /** @type {Record<string, ExpandTypeReturnType>} */
2636
2641
  var templates = Object.create(null);
2642
+ // `@template {Constraint} [Name=Default]` (or `[Name]`): constraint wins,
2643
+ // the default only matters when nothing is inferred.
2644
+ var regexTemplateConstrainedDefault = /@template \{(.*?)\}\s*\[([a-zA-Z0-9_$]+)(?:=([^\]]+))?\]/g;
2645
+ var _iterator = _createForOfIteratorHelper(src.matchAll(regexTemplateConstrainedDefault)),
2646
+ _step;
2647
+ try {
2648
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2649
+ var match = _step.value;
2650
+ templates[match[2]] = expandType(match[1].trim());
2651
+ }
2652
+ } catch (err) {
2653
+ _iterator.e(err);
2654
+ } finally {
2655
+ _iterator.f();
2656
+ }
2637
2657
  matches.forEach(function (_) {
2638
2658
  var type = expandType(_[1].trim());
2639
2659
  var name = _[2].trim();
@@ -2641,34 +2661,34 @@
2641
2661
  });
2642
2662
  // `@template [A=X]` defaults: constraint X under name A.
2643
2663
  var regexTemplateDefault = /@template \[([a-zA-Z0-9_$]+)=([^\]]+)\]/g;
2644
- var _iterator = _createForOfIteratorHelper(src.matchAll(regexTemplateDefault)),
2645
- _step;
2664
+ var _iterator2 = _createForOfIteratorHelper(src.matchAll(regexTemplateDefault)),
2665
+ _step2;
2646
2666
  try {
2647
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
2648
- var match = _step.value;
2649
- templates[match[1]] = expandType(match[2].trim());
2667
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2668
+ var _match = _step2.value;
2669
+ templates[_match[1]] = expandType(_match[2].trim());
2650
2670
  }
2651
2671
  // Bare `@template T`: unconstrained, stands in as any.
2652
2672
  } catch (err) {
2653
- _iterator.e(err);
2673
+ _iterator2.e(err);
2654
2674
  } finally {
2655
- _iterator.f();
2675
+ _iterator2.f();
2656
2676
  }
2657
2677
  var regexTemplateBare = /@template ([a-zA-Z0-9_$]+)(?![a-zA-Z0-9_$])/g;
2658
- var _iterator2 = _createForOfIteratorHelper(src.matchAll(regexTemplateBare)),
2659
- _step2;
2678
+ var _iterator3 = _createForOfIteratorHelper(src.matchAll(regexTemplateBare)),
2679
+ _step3;
2660
2680
  try {
2661
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2662
- var _match = _step2.value;
2663
- var name = _match[1];
2681
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2682
+ var _match2 = _step3.value;
2683
+ var name = _match2[1];
2664
2684
  if (!(name in templates)) {
2665
2685
  templates[name] = 'any';
2666
2686
  }
2667
2687
  }
2668
2688
  } catch (err) {
2669
- _iterator2.e(err);
2689
+ _iterator3.e(err);
2670
2690
  } finally {
2671
- _iterator2.f();
2691
+ _iterator3.f();
2672
2692
  }
2673
2693
  if (!Object.keys(templates).length) {
2674
2694
  return;
@@ -4938,6 +4958,10 @@
4938
4958
  * @property {string} [filename] - The name of a file to which the instance pertains.
4939
4959
  * @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
4940
4960
  * @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
4961
+ * @property {string} [projectVersion] - Host project version funneled into
4962
+ * the emitted header via `setProjectVersion(...)`, so `Download log` meta
4963
+ * identifies the app build. Bundler plugins resolve it from their own
4964
+ * option or the host package.json.
4941
4965
  */
4942
4966
  var Asserter = /*#__PURE__*/function (_Stringifier) {
4943
4967
  _inherits(Asserter, _Stringifier);
@@ -4960,7 +4984,8 @@
4960
4984
  _ref$addHeader = _ref.addHeader,
4961
4985
  addHeader = _ref$addHeader === void 0 ? true : _ref$addHeader,
4962
4986
  _ref$ignoreLocations = _ref.ignoreLocations,
4963
- ignoreLocations = _ref$ignoreLocations === void 0 ? [] : _ref$ignoreLocations;
4987
+ ignoreLocations = _ref$ignoreLocations === void 0 ? [] : _ref$ignoreLocations,
4988
+ projectVersion = _ref.projectVersion;
4964
4989
  _classCallCheck(this, Asserter);
4965
4990
  _this = _super.call(this);
4966
4991
  /** @type {Record<string, Stat>} */
@@ -5025,6 +5050,7 @@
5025
5050
  _this.filename = filename;
5026
5051
  _this.addHeader = addHeader;
5027
5052
  _this.ignoreLocations = ignoreLocations;
5053
+ _this.projectVersion = projectVersion;
5028
5054
  return _this;
5029
5055
  }
5030
5056
  _createClass(Asserter, [{
@@ -5133,9 +5159,16 @@
5133
5159
  if (this.validateDivision) {
5134
5160
  header += ", validateDivision";
5135
5161
  }
5136
- header += ", registerTypedef, registerClass, registerImportNamespaceSpecifier} from '@runtime-type-inspector/runtime';\n";
5162
+ header += ", registerTypedef, registerClass, registerImportNamespaceSpecifier";
5163
+ if (this.projectVersion !== undefined && this.projectVersion !== null) {
5164
+ header += ", setProjectVersion";
5165
+ }
5166
+ header += "} from '@runtime-type-inspector/runtime';\n";
5137
5167
  // Prevent tree-shaking in UMD build so we can always "add a breakpoint here".
5138
5168
  header += "export * from '@runtime-type-inspector/runtime';\n";
5169
+ if (this.projectVersion !== undefined && this.projectVersion !== null) {
5170
+ header += "setProjectVersion(".concat(JSON.stringify(this.projectVersion), ");\n");
5171
+ }
5139
5172
  return header;
5140
5173
  }
5141
5174
  /**
@@ -5350,6 +5383,115 @@
5350
5383
  params: params
5351
5384
  };
5352
5385
  }
5386
+ /**
5387
+ * Reads `@template` bindings from every lexically enclosing scope, outer
5388
+ * to inner: classes (declaration or expression, incl. `export` wrappers)
5389
+ * and functions with their own `@template` tags. Class templates scope
5390
+ * over the constructor and methods, mirroring TypeScript:
5391
+ * `new Asset('x', ...)` infers `K` from the constructor's `@param {K}`
5392
+ * occurrences. Inner scopes shadow outer names on conflict.
5393
+ * @param {Node} node - The function or block node being checked.
5394
+ * @returns {Record<string, any>|undefined} Merged scope templates or undefined.
5395
+ */
5396
+ }, {
5397
+ key: "getScopeTemplates",
5398
+ value: function getScopeTemplates(node) {
5399
+ var fnNode = node;
5400
+ if (node.type === 'BlockStatement') {
5401
+ fnNode = this.parent;
5402
+ }
5403
+ var anchorIdx = this.parents.findLastIndex(function (_) {
5404
+ return _ === node;
5405
+ });
5406
+ if (anchorIdx === -1) {
5407
+ return;
5408
+ }
5409
+ var merged;
5410
+ for (var i = 0; i <= anchorIdx; i++) {
5411
+ var ancestor = this.parents[i];
5412
+ if (!ancestor || ancestor === fnNode) {
5413
+ // Never inherit the node's own bindings: they merge separately and win.
5414
+ continue;
5415
+ }
5416
+ var templates = void 0;
5417
+ if (ancestor.type === 'ClassDeclaration' || ancestor.type === 'ClassExpression') {
5418
+ templates = this.classTemplatesOf(ancestor);
5419
+ } else if (nodeIsFunctionLike(ancestor)) {
5420
+ templates = this.functionTemplatesOf(ancestor);
5421
+ }
5422
+ if (templates) {
5423
+ merged = _objectSpread2(_objectSpread2({}, merged), templates);
5424
+ }
5425
+ }
5426
+ return merged;
5427
+ }
5428
+ /**
5429
+ * Reads the `@template` bindings off one class node.
5430
+ * @param {Node} classDecl - The ClassDeclaration or ClassExpression node.
5431
+ * @returns {Record<string, any>|undefined} Its templates or undefined.
5432
+ */
5433
+ }, {
5434
+ key: "classTemplatesOf",
5435
+ value: function classTemplatesOf(classDecl) {
5436
+ var _leadingComments;
5437
+ var leadingComments = classDecl.leadingComments;
5438
+ if (!leadingComments) {
5439
+ var exportNamed = this.findParentOfType(classDecl, 'ExportNamedDeclaration');
5440
+ leadingComments = exportNamed === null || exportNamed === void 0 ? void 0 : exportNamed.leadingComments;
5441
+ if (!leadingComments) {
5442
+ var exportDefault = this.findParentOfType(classDecl, 'ExportDefaultDeclaration');
5443
+ leadingComments = exportDefault === null || exportDefault === void 0 ? void 0 : exportDefault.leadingComments;
5444
+ }
5445
+ if (!leadingComments && classDecl.type === 'ClassExpression') {
5446
+ // `/** @template K */ const Box = class {...}`: the comment lives on
5447
+ // the declaration, mirroring function-expression handling.
5448
+ var varDecl = this.findParentOfType(classDecl, 'VariableDeclaration');
5449
+ leadingComments = varDecl === null || varDecl === void 0 ? void 0 : varDecl.leadingComments;
5450
+ }
5451
+ }
5452
+ if (!((_leadingComments = leadingComments) !== null && _leadingComments !== void 0 && _leadingComments.length)) {
5453
+ return;
5454
+ }
5455
+ var lastComment = leadingComments[leadingComments.length - 1];
5456
+ if (lastComment.type !== 'CommentBlock') {
5457
+ return;
5458
+ }
5459
+ return parseJSDocTemplates(lastComment.value);
5460
+ }
5461
+ /**
5462
+ * Reads the `@template` bindings off one enclosing function: its own
5463
+ * leading comment, resolving the same `export`/declarator wrappers the
5464
+ * node's own JSDoc lookup uses.
5465
+ * @param {Node} fnNode - The enclosing function-like node.
5466
+ * @returns {Record<string, any>|undefined} Its templates or undefined.
5467
+ */
5468
+ }, {
5469
+ key: "functionTemplatesOf",
5470
+ value: function functionTemplatesOf(fnNode) {
5471
+ var _leadingComments2;
5472
+ var leadingComments;
5473
+ if (fnNode.type === 'FunctionDeclaration') {
5474
+ var _fnNode$leadingCommen, _this$findParentOfTyp;
5475
+ leadingComments = (_fnNode$leadingCommen = fnNode.leadingComments) !== null && _fnNode$leadingCommen !== void 0 ? _fnNode$leadingCommen : (_this$findParentOfTyp = this.findParentOfType(fnNode, 'ExportNamedDeclaration')) === null || _this$findParentOfTyp === void 0 ? void 0 : _this$findParentOfTyp.leadingComments;
5476
+ } else if (fnNode.type === 'FunctionExpression') {
5477
+ var _this$getLeadingComme;
5478
+ leadingComments = (_this$getLeadingComme = this.getLeadingCommentsNodeForFunctionExpression(fnNode)) === null || _this$getLeadingComme === void 0 ? void 0 : _this$getLeadingComme.leadingComments;
5479
+ } else if (fnNode.type === 'ArrowFunctionExpression') {
5480
+ var _this$getLeadingComme2;
5481
+ leadingComments = (_this$getLeadingComme2 = this.getLeadingCommentsNodeForArrowFunctionExpression(fnNode)) === null || _this$getLeadingComme2 === void 0 ? void 0 : _this$getLeadingComme2.leadingComments;
5482
+ } else {
5483
+ // ObjectMethod, ClassMethod, ClassPrivateMethod: docs sit on the node.
5484
+ leadingComments = fnNode.leadingComments;
5485
+ }
5486
+ if (!((_leadingComments2 = leadingComments) !== null && _leadingComments2 !== void 0 && _leadingComments2.length)) {
5487
+ return;
5488
+ }
5489
+ var lastComment = leadingComments[leadingComments.length - 1];
5490
+ if (lastComment.type !== 'CommentBlock') {
5491
+ return;
5492
+ }
5493
+ return parseJSDocTemplates(lastComment.value);
5494
+ }
5353
5495
  /**
5354
5496
  * Retrieves the name of a parameter from a Babel AST node.
5355
5497
  *
@@ -5497,7 +5639,7 @@
5497
5639
  stat.checked++;
5498
5640
  return this.emitDefaultChecks(node, inferred, true);
5499
5641
  }
5500
- var templates = jsdoc.templates,
5642
+ var ownTemplates = jsdoc.templates,
5501
5643
  params = jsdoc.params;
5502
5644
  if (!params) {
5503
5645
  console.warn("This should never happen, please check your input code.", this.getLeadingComment(node), {
@@ -5506,6 +5648,11 @@
5506
5648
  stat.unchecked++;
5507
5649
  return '';
5508
5650
  }
5651
+ // Lexically enclosing `@template`s (classes and functions) scope over
5652
+ // this node: merge them under method-local templates so `K` in
5653
+ // `@param {K}` resolves jointly. Inner scopes shadow outer ones.
5654
+ var scopeTemplates = this.getScopeTemplates(node);
5655
+ var templates = scopeTemplates ? _objectSpread2(_objectSpread2({}, scopeTemplates), ownTemplates) : ownTemplates;
5509
5656
  stat.checked++;
5510
5657
  var spaces = this.spaces;
5511
5658
  var out = '';
@@ -6593,6 +6740,11 @@
6593
6740
  argument: keyofArg
6594
6741
  };
6595
6742
  }
6743
+ if (node.operator === 'unique') {
6744
+ // `unique symbol` is a symbol at runtime; uniqueness refers to a
6745
+ // specific declaration which we cannot track here.
6746
+ return toSourceBabelTS(node.typeAnnotation);
6747
+ }
6596
6748
  console.warn('unimplemented TSTypeOperator', node.operator);
6597
6749
  return 'any';
6598
6750
  case 'TSQualifiedName':
package/index.mjs CHANGED
@@ -1,6 +1,21 @@
1
1
  import { parse } from '@babel/parser';
2
2
  import ts from 'typescript';
3
3
 
4
+ function _extends() {
5
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
6
+ for (var i = 1; i < arguments.length; i++) {
7
+ var source = arguments[i];
8
+ for (var key in source) {
9
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
10
+ target[key] = source[key];
11
+ }
12
+ }
13
+ }
14
+ return target;
15
+ };
16
+ return _extends.apply(this, arguments);
17
+ }
18
+
4
19
  /**
5
20
  * @typedef DocType
6
21
  * @property {boolean} optional - Type is optional.
@@ -406,6 +421,11 @@ function toSourceTS(node) {
406
421
  // readonly erased at runtime, same shape as the inner type.
407
422
  return toSourceTS(node.type);
408
423
  }
424
+ if (node.operator === UniqueKeyword) {
425
+ // `unique symbol` is a symbol at runtime; the uniqueness refers to
426
+ // a specific declaration which we cannot track here.
427
+ return toSourceTS(node.type);
428
+ }
409
429
  console.warn("unimplemented TypeOperator", node);
410
430
  return 'any';
411
431
  case TypeReference:
@@ -1119,7 +1139,7 @@ function expandTypeDepFree(type) {
1119
1139
  return expandTypeDepFree(type.slice(9).trim());
1120
1140
  }
1121
1141
  if (type === 'unique symbol') {
1122
- return 'any';
1142
+ return 'symbol';
1123
1143
  }
1124
1144
  // Conditionals before nullable: `A extends B ? C : D?` must keep the
1125
1145
  // nullable on the false branch, not lift it over the whole condition.
@@ -1365,21 +1385,6 @@ function expandTypeDepFree(type) {
1365
1385
  return type;
1366
1386
  }
1367
1387
 
1368
- function _extends() {
1369
- _extends = Object.assign ? Object.assign.bind() : function (target) {
1370
- for (var i = 1; i < arguments.length; i++) {
1371
- var source = arguments[i];
1372
- for (var key in source) {
1373
- if (Object.prototype.hasOwnProperty.call(source, key)) {
1374
- target[key] = source[key];
1375
- }
1376
- }
1377
- }
1378
- return target;
1379
- };
1380
- return _extends.apply(this, arguments);
1381
- }
1382
-
1383
1388
  /**
1384
1389
  * Extracts the parameter name and its optionality from a JSDoc parameter string.
1385
1390
  *
@@ -2303,6 +2308,12 @@ function parseJSDocTemplates(src, expandType = expandTypeDepFree) {
2303
2308
  const matches = [...src.matchAll(regexTemplateTyped)];
2304
2309
  /** @type {Record<string, ExpandTypeReturnType>} */
2305
2310
  const templates = Object.create(null);
2311
+ // `@template {Constraint} [Name=Default]` (or `[Name]`): constraint wins,
2312
+ // the default only matters when nothing is inferred.
2313
+ const regexTemplateConstrainedDefault = /@template \{(.*?)\}\s*\[([a-zA-Z0-9_$]+)(?:=([^\]]+))?\]/g;
2314
+ for (const match of src.matchAll(regexTemplateConstrainedDefault)) {
2315
+ templates[match[2]] = expandType(match[1].trim());
2316
+ }
2306
2317
  matches.forEach(_ => {
2307
2318
  const type = expandType(_[1].trim());
2308
2319
  const name = _[2].trim();
@@ -4515,6 +4526,10 @@ class Stringifier {
4515
4526
  * @property {string} [filename] - The name of a file to which the instance pertains.
4516
4527
  * @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
4517
4528
  * @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
4529
+ * @property {string} [projectVersion] - Host project version funneled into
4530
+ * the emitted header via `setProjectVersion(...)`, so `Download log` meta
4531
+ * identifies the app build. Bundler plugins resolve it from their own
4532
+ * option or the host package.json.
4518
4533
  */
4519
4534
  class Asserter extends Stringifier {
4520
4535
  /**
@@ -4527,7 +4542,8 @@ class Asserter extends Stringifier {
4527
4542
  expandType = expandTypeDepFree,
4528
4543
  filename,
4529
4544
  addHeader = true,
4530
- ignoreLocations = []
4545
+ ignoreLocations = [],
4546
+ projectVersion
4531
4547
  } = {}) {
4532
4548
  super();
4533
4549
  /** @type {Record<string, Stat>} */
@@ -4592,6 +4608,7 @@ class Asserter extends Stringifier {
4592
4608
  this.filename = filename;
4593
4609
  this.addHeader = addHeader;
4594
4610
  this.ignoreLocations = ignoreLocations;
4611
+ this.projectVersion = projectVersion;
4595
4612
  }
4596
4613
  /**
4597
4614
  * We expand type-asserted ArrowFunctionExpressions in order to add type assertions.
@@ -4694,9 +4711,16 @@ class Asserter extends Stringifier {
4694
4711
  if (this.validateDivision) {
4695
4712
  header += ", validateDivision";
4696
4713
  }
4697
- header += ", registerTypedef, registerClass, registerImportNamespaceSpecifier} from '@runtime-type-inspector/runtime';\n";
4714
+ header += ", registerTypedef, registerClass, registerImportNamespaceSpecifier";
4715
+ if (this.projectVersion !== undefined && this.projectVersion !== null) {
4716
+ header += ", setProjectVersion";
4717
+ }
4718
+ header += "} from '@runtime-type-inspector/runtime';\n";
4698
4719
  // Prevent tree-shaking in UMD build so we can always "add a breakpoint here".
4699
4720
  header += "export * from '@runtime-type-inspector/runtime';\n";
4721
+ if (this.projectVersion !== undefined && this.projectVersion !== null) {
4722
+ header += `setProjectVersion(${JSON.stringify(this.projectVersion)});\n`;
4723
+ }
4700
4724
  return header;
4701
4725
  }
4702
4726
  /**
@@ -4903,6 +4927,107 @@ class Asserter extends Stringifier {
4903
4927
  params
4904
4928
  };
4905
4929
  }
4930
+ /**
4931
+ * Reads `@template` bindings from every lexically enclosing scope, outer
4932
+ * to inner: classes (declaration or expression, incl. `export` wrappers)
4933
+ * and functions with their own `@template` tags. Class templates scope
4934
+ * over the constructor and methods, mirroring TypeScript:
4935
+ * `new Asset('x', ...)` infers `K` from the constructor's `@param {K}`
4936
+ * occurrences. Inner scopes shadow outer names on conflict.
4937
+ * @param {Node} node - The function or block node being checked.
4938
+ * @returns {Record<string, any>|undefined} Merged scope templates or undefined.
4939
+ */
4940
+ getScopeTemplates(node) {
4941
+ let fnNode = node;
4942
+ if (node.type === 'BlockStatement') {
4943
+ fnNode = this.parent;
4944
+ }
4945
+ const anchorIdx = this.parents.findLastIndex(_ => _ === node);
4946
+ if (anchorIdx === -1) {
4947
+ return;
4948
+ }
4949
+ let merged;
4950
+ for (let i = 0; i <= anchorIdx; i++) {
4951
+ const ancestor = this.parents[i];
4952
+ if (!ancestor || ancestor === fnNode) {
4953
+ // Never inherit the node's own bindings: they merge separately and win.
4954
+ continue;
4955
+ }
4956
+ let templates;
4957
+ if (ancestor.type === 'ClassDeclaration' || ancestor.type === 'ClassExpression') {
4958
+ templates = this.classTemplatesOf(ancestor);
4959
+ } else if (nodeIsFunctionLike(ancestor)) {
4960
+ templates = this.functionTemplatesOf(ancestor);
4961
+ }
4962
+ if (templates) {
4963
+ merged = _extends({}, merged, templates);
4964
+ }
4965
+ }
4966
+ return merged;
4967
+ }
4968
+ /**
4969
+ * Reads the `@template` bindings off one class node.
4970
+ * @param {Node} classDecl - The ClassDeclaration or ClassExpression node.
4971
+ * @returns {Record<string, any>|undefined} Its templates or undefined.
4972
+ */
4973
+ classTemplatesOf(classDecl) {
4974
+ var _leadingComments;
4975
+ let leadingComments = classDecl.leadingComments;
4976
+ if (!leadingComments) {
4977
+ const exportNamed = this.findParentOfType(classDecl, 'ExportNamedDeclaration');
4978
+ leadingComments = exportNamed == null ? void 0 : exportNamed.leadingComments;
4979
+ if (!leadingComments) {
4980
+ const exportDefault = this.findParentOfType(classDecl, 'ExportDefaultDeclaration');
4981
+ leadingComments = exportDefault == null ? void 0 : exportDefault.leadingComments;
4982
+ }
4983
+ if (!leadingComments && classDecl.type === 'ClassExpression') {
4984
+ // `/** @template K */ const Box = class {...}`: the comment lives on
4985
+ // the declaration, mirroring function-expression handling.
4986
+ const varDecl = this.findParentOfType(classDecl, 'VariableDeclaration');
4987
+ leadingComments = varDecl == null ? void 0 : varDecl.leadingComments;
4988
+ }
4989
+ }
4990
+ if (!((_leadingComments = leadingComments) != null && _leadingComments.length)) {
4991
+ return;
4992
+ }
4993
+ const lastComment = leadingComments[leadingComments.length - 1];
4994
+ if (lastComment.type !== 'CommentBlock') {
4995
+ return;
4996
+ }
4997
+ return parseJSDocTemplates(lastComment.value);
4998
+ }
4999
+ /**
5000
+ * Reads the `@template` bindings off one enclosing function: its own
5001
+ * leading comment, resolving the same `export`/declarator wrappers the
5002
+ * node's own JSDoc lookup uses.
5003
+ * @param {Node} fnNode - The enclosing function-like node.
5004
+ * @returns {Record<string, any>|undefined} Its templates or undefined.
5005
+ */
5006
+ functionTemplatesOf(fnNode) {
5007
+ var _leadingComments2;
5008
+ let leadingComments;
5009
+ if (fnNode.type === 'FunctionDeclaration') {
5010
+ var _fnNode$leadingCommen, _this$findParentOfTyp;
5011
+ leadingComments = (_fnNode$leadingCommen = fnNode.leadingComments) != null ? _fnNode$leadingCommen : (_this$findParentOfTyp = this.findParentOfType(fnNode, 'ExportNamedDeclaration')) == null ? void 0 : _this$findParentOfTyp.leadingComments;
5012
+ } else if (fnNode.type === 'FunctionExpression') {
5013
+ var _this$getLeadingComme;
5014
+ leadingComments = (_this$getLeadingComme = this.getLeadingCommentsNodeForFunctionExpression(fnNode)) == null ? void 0 : _this$getLeadingComme.leadingComments;
5015
+ } else if (fnNode.type === 'ArrowFunctionExpression') {
5016
+ var _this$getLeadingComme2;
5017
+ leadingComments = (_this$getLeadingComme2 = this.getLeadingCommentsNodeForArrowFunctionExpression(fnNode)) == null ? void 0 : _this$getLeadingComme2.leadingComments;
5018
+ } else {
5019
+ // ObjectMethod, ClassMethod, ClassPrivateMethod: docs sit on the node.
5020
+ leadingComments = fnNode.leadingComments;
5021
+ }
5022
+ if (!((_leadingComments2 = leadingComments) != null && _leadingComments2.length)) {
5023
+ return;
5024
+ }
5025
+ const lastComment = leadingComments[leadingComments.length - 1];
5026
+ if (lastComment.type !== 'CommentBlock') {
5027
+ return;
5028
+ }
5029
+ return parseJSDocTemplates(lastComment.value);
5030
+ }
4906
5031
  /**
4907
5032
  * Retrieves the name of a parameter from a Babel AST node.
4908
5033
  *
@@ -5051,7 +5176,7 @@ class Asserter extends Stringifier {
5051
5176
  return this.emitDefaultChecks(node, inferred, true);
5052
5177
  }
5053
5178
  const {
5054
- templates,
5179
+ templates: ownTemplates,
5055
5180
  params
5056
5181
  } = jsdoc;
5057
5182
  if (!params) {
@@ -5061,6 +5186,11 @@ class Asserter extends Stringifier {
5061
5186
  stat.unchecked++;
5062
5187
  return '';
5063
5188
  }
5189
+ // Lexically enclosing `@template`s (classes and functions) scope over
5190
+ // this node: merge them under method-local templates so `K` in
5191
+ // `@param {K}` resolves jointly. Inner scopes shadow outer ones.
5192
+ const scopeTemplates = this.getScopeTemplates(node);
5193
+ const templates = scopeTemplates ? _extends({}, scopeTemplates, ownTemplates) : ownTemplates;
5064
5194
  stat.checked++;
5065
5195
  const {
5066
5196
  spaces
@@ -6058,6 +6188,11 @@ function toSourceBabelTS(node) {
6058
6188
  argument: keyofArg
6059
6189
  };
6060
6190
  }
6191
+ if (node.operator === 'unique') {
6192
+ // `unique symbol` is a symbol at runtime; uniqueness refers to a
6193
+ // specific declaration which we cannot track here.
6194
+ return toSourceBabelTS(node.typeAnnotation);
6195
+ }
6061
6196
  console.warn('unimplemented TSTypeOperator', node.operator);
6062
6197
  return 'any';
6063
6198
  case 'TSQualifiedName':
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "ts2js": "ts2js.js"
5
5
  },
6
6
  "name": "@runtime-type-inspector/transpiler",
7
- "version": "5.0.3",
7
+ "version": "5.0.5",
8
8
  "description": "Validating JSDoc types at runtime for high-quality types - Trust is good, control is better.",
9
9
  "main": "index.cjs",
10
10
  "module": "index.mjs",
@@ -22,7 +22,7 @@
22
22
  "homepage": "https://github.com/kungfooman/RuntimeTypeInspector.js#readme",
23
23
  "dependencies": {
24
24
  "@babel/parser": "^7.23.0",
25
- "@runtime-type-inspector/runtime": "^5.0.3",
25
+ "@runtime-type-inspector/runtime": "^5.0.5",
26
26
  "typescript": "^5.1.6"
27
27
  },
28
28
  "files": [