@runtime-type-inspector/transpiler 5.0.1 → 5.0.2

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 +1509 -288
  2. package/index.mjs +1406 -280
  3. package/package.json +2 -2
package/index.cjs CHANGED
@@ -427,6 +427,7 @@
427
427
  PropertySignature = _ts$SyntaxKind.PropertySignature,
428
428
  StringKeyword = _ts$SyntaxKind.StringKeyword,
429
429
  StringLiteral = _ts$SyntaxKind.StringLiteral,
430
+ SymbolKeyword = _ts$SyntaxKind.SymbolKeyword,
430
431
  ThisType = _ts$SyntaxKind.ThisType,
431
432
  TupleType = _ts$SyntaxKind.TupleType,
432
433
  TypeLiteral = _ts$SyntaxKind.TypeLiteral,
@@ -453,6 +454,8 @@
453
454
  var ConstructorType = _ts$SyntaxKind.ConstructorType,
454
455
  NamedTupleMember = _ts$SyntaxKind.NamedTupleMember,
455
456
  MappedType = _ts$SyntaxKind.MappedType,
457
+ MinusToken = _ts$SyntaxKind.MinusToken,
458
+ PlusToken = _ts$SyntaxKind.PlusToken,
456
459
  TypeParameter = _ts$SyntaxKind.TypeParameter,
457
460
  QualifiedName = _ts$SyntaxKind.QualifiedName,
458
461
  TemplateLiteralType = _ts$SyntaxKind.TemplateLiteralType,
@@ -565,12 +568,26 @@
565
568
  // For example: {[K in TaskType]: InstanceType etc.
566
569
  var iterable = toSourceTS(parameter.constraint); // TaskType
567
570
  var element = toSourceTS(parameter.name); // K
568
- return {
571
+ var out = {
569
572
  type: 'mapping',
570
573
  iterable: iterable,
571
574
  element: element,
572
575
  result: result
573
576
  };
577
+ if (node.nameType) {
578
+ // `as` key remapping, e.g. {[K in keyof T as K extends string ? K : never]: ...}
579
+ out.nameType = toSourceTS(node.nameType);
580
+ }
581
+ // Modifiers: `-?` strips optionality, `+?`/`?` force it;
582
+ // `-readonly` strips readonly, `+readonly`/`readonly` force it.
583
+ // Absent modifiers preserve the source behavior (see createTypeFromMapping).
584
+ if (node.questionToken) {
585
+ out.question = node.questionToken.kind === MinusToken ? '-' : node.questionToken.kind === PlusToken ? '+' : '?';
586
+ }
587
+ if (node.readonlyToken) {
588
+ out.readonly = node.readonlyToken.kind === MinusToken ? '-' : node.readonlyToken.kind === PlusToken ? '+' : 'readonly';
589
+ }
590
+ return out;
574
591
  }
575
592
  console.warn("MappedType: expected TypeParameter");
576
593
  return 'transpiler-error';
@@ -797,6 +814,15 @@
797
814
  optional: true
798
815
  };
799
816
  }
817
+ if (Array.isArray(member.modifiers) && member.modifiers.some(function (modifier) {
818
+ return modifier.kind === ReadonlyKeyword;
819
+ })) {
820
+ // Tracked for IfEquals-style comparisons; ignored by validation.
821
+ if (_type && _typeof(_type) === 'object') _type.readonly = true;else _type = {
822
+ type: _type,
823
+ readonly: true
824
+ };
825
+ }
800
826
  properties[_name2] = _type;
801
827
  } else {
802
828
  console.warn('TypeLiteral: unhandled member', member);
@@ -856,6 +882,7 @@
856
882
  case AnyKeyword:
857
883
  case BooleanKeyword:
858
884
  case StringKeyword:
885
+ case SymbolKeyword:
859
886
  case NeverKeyword:
860
887
  case NullKeyword:
861
888
  case NumberKeyword:
@@ -936,6 +963,7 @@
936
963
  */
937
964
  /**
938
965
  * Splits a string by a delimiter, ignoring delimiters nested inside <>, {}, [], ().
966
+ * Quote-aware: delimiters inside '...', "..." or `...` never split.
939
967
  * @param {string} str - The string to split.
940
968
  * @param {string} delimiter - Single character delimiter.
941
969
  * @returns {string[]} Top-level split parts.
@@ -946,28 +974,148 @@
946
974
  var depthCurly = 0;
947
975
  var depthSquare = 0;
948
976
  var depthParen = 0;
977
+ var inSingle = false;
978
+ var inDouble = false;
979
+ var inBacktick = false;
949
980
  var current = '';
950
- var _iterator = _createForOfIteratorHelper(str),
951
- _step;
952
- try {
953
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
954
- var c = _step.value;
981
+ for (var i = 0; i < str.length; i++) {
982
+ var c = str[i];
983
+ var prev = i > 0 ? str[i - 1] : '';
984
+ if (c === "'" && !inDouble && !inBacktick && prev !== '\\') inSingle = !inSingle;else if (c === '"' && !inSingle && !inBacktick && prev !== '\\') inDouble = !inDouble;else if (c === '`' && !inSingle && !inDouble && prev !== '\\') inBacktick = !inBacktick;
985
+ var inQuote = inSingle || inDouble || inBacktick;
986
+ if (!inQuote) {
955
987
  if (c === '<') depthAngle++;else if (c === '>') depthAngle--;else if (c === '{') depthCurly++;else if (c === '}') depthCurly--;else if (c === '[') depthSquare++;else if (c === ']') depthSquare--;else if (c === '(') depthParen++;else if (c === ')') depthParen--;
956
- if (c === delimiter && depthAngle === 0 && depthCurly === 0 && depthSquare === 0 && depthParen === 0) {
957
- parts.push(current);
958
- current = '';
959
- } else {
960
- current += c;
961
- }
962
988
  }
963
- } catch (err) {
964
- _iterator.e(err);
965
- } finally {
966
- _iterator.f();
989
+ if (c === delimiter && !inQuote && depthAngle === 0 && depthCurly === 0 && depthSquare === 0 && depthParen === 0) {
990
+ parts.push(current);
991
+ current = '';
992
+ } else {
993
+ current += c;
994
+ }
967
995
  }
968
996
  parts.push(current);
969
997
  return parts;
970
998
  }
999
+ /**
1000
+ * Depth state at a given index, quote-aware.
1001
+ * @param {string} str - The string to scan.
1002
+ * @param {number} upto - Exclusive end index.
1003
+ * @returns {{angle: number, curly: number, square: number, paren: number, quote: boolean}} Depths.
1004
+ */
1005
+ function depthsAt(str, upto) {
1006
+ var angle = 0;
1007
+ var curly = 0;
1008
+ var square = 0;
1009
+ var paren = 0;
1010
+ var inSingle = false;
1011
+ var inDouble = false;
1012
+ var inBacktick = false;
1013
+ for (var i = 0; i < upto; i++) {
1014
+ var c = str[i];
1015
+ var prev = i > 0 ? str[i - 1] : '';
1016
+ if (c === "'" && !inDouble && !inBacktick && prev !== '\\') inSingle = !inSingle;else if (c === '"' && !inSingle && !inBacktick && prev !== '\\') inDouble = !inDouble;else if (c === '`' && !inSingle && !inDouble && prev !== '\\') inBacktick = !inBacktick;
1017
+ var inQuote = inSingle || inDouble || inBacktick;
1018
+ if (inQuote) continue;
1019
+ if (c === '<') angle++;else if (c === '>') angle--;else if (c === '{') curly++;else if (c === '}') curly--;else if (c === '[') square++;else if (c === ']') square--;else if (c === '(') paren++;else if (c === ')') paren--;
1020
+ }
1021
+ return {
1022
+ angle: angle,
1023
+ curly: curly,
1024
+ square: square,
1025
+ paren: paren,
1026
+ quote: inSingle || inDouble || inBacktick
1027
+ };
1028
+ }
1029
+ /**
1030
+ * Finds a keyword (e.g. `in`, `as`, `extends`) at top level, outside brackets/quotes.
1031
+ * @param {string} str - The string to search.
1032
+ * @param {string} keyword - Keyword without surrounding spaces.
1033
+ * @returns {number} Start index or -1.
1034
+ */
1035
+ function findTopLevelKeyword(str, keyword) {
1036
+ var isWord = function isWord(c) {
1037
+ return /[A-Za-z0-9_$]/.test(c);
1038
+ };
1039
+ for (var i = 0; i <= str.length - keyword.length; i++) {
1040
+ if (str.slice(i, i + keyword.length) !== keyword) continue;
1041
+ var before = i > 0 ? str[i - 1] : ' ';
1042
+ var after = i + keyword.length < str.length ? str[i + keyword.length] : ' ';
1043
+ if (isWord(before) || isWord(after)) continue;
1044
+ var d = depthsAt(str, i);
1045
+ if (d.angle === 0 && d.curly === 0 && d.square === 0 && d.paren === 0 && !d.quote) {
1046
+ // Ensure remainder after keyword is also top-level (keyword itself not quoted).
1047
+ var d2 = depthsAt(str, i + keyword.length);
1048
+ if (!d2.quote) return i;
1049
+ }
1050
+ }
1051
+ return -1;
1052
+ }
1053
+ /**
1054
+ * Finds a single-char delimiter at top level, outside brackets/quotes.
1055
+ * @param {string} str - The string to search.
1056
+ * @param {string} delimiter - Single character.
1057
+ * @param {number} from - Start index.
1058
+ * @returns {number} Index or -1.
1059
+ */
1060
+ function findTopLevelChar(str, delimiter) {
1061
+ var from = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
1062
+ var angle = 0;
1063
+ var curly = 0;
1064
+ var square = 0;
1065
+ var paren = 0;
1066
+ var inSingle = false;
1067
+ var inDouble = false;
1068
+ var inBacktick = false;
1069
+ for (var i = 0; i < str.length; i++) {
1070
+ var c = str[i];
1071
+ // Check at depths *before* the current char, so `[` itself is findable.
1072
+ var inQuoteBefore = inSingle || inDouble || inBacktick;
1073
+ if (i >= from && c === delimiter && !inQuoteBefore && angle === 0 && curly === 0 && square === 0 && paren === 0) {
1074
+ return i;
1075
+ }
1076
+ var prev = i > 0 ? str[i - 1] : '';
1077
+ if (c === "'" && !inDouble && !inBacktick && prev !== '\\') inSingle = !inSingle;else if (c === '"' && !inSingle && !inBacktick && prev !== '\\') inDouble = !inDouble;else if (c === '`' && !inSingle && !inDouble && prev !== '\\') inBacktick = !inBacktick;
1078
+ var inQuote = inSingle || inDouble || inBacktick;
1079
+ if (!inQuote) {
1080
+ if (c === '<') angle++;else if (c === '>') angle--;else if (c === '{') curly++;else if (c === '}') curly--;else if (c === '[') square++;else if (c === ']') square--;else if (c === '(') paren++;else if (c === ')') paren--;
1081
+ }
1082
+ }
1083
+ return -1;
1084
+ }
1085
+ /**
1086
+ * Strips balanced outer parens, e.g. `(( T ))` -> `T`.
1087
+ * @param {string} type - Trimmed type string.
1088
+ * @returns {string} Unwrapped type.
1089
+ */
1090
+ function stripOuterParens(type) {
1091
+ var changed = true;
1092
+ while (changed) {
1093
+ changed = false;
1094
+ if (type.length >= 2 && type[0] === '(' && type[type.length - 1] === ')') {
1095
+ var depth = 0;
1096
+ var balanced = true;
1097
+ var inSingle = false;
1098
+ var inDouble = false;
1099
+ for (var i = 0; i < type.length; i++) {
1100
+ var c = type[i];
1101
+ if (c === "'" && !inDouble && type[i - 1] !== '\\') inSingle = !inSingle;else if (c === '"' && !inSingle && type[i - 1] !== '\\') inDouble = !inDouble;else if (!inSingle && !inDouble) {
1102
+ if (c === '(') depth++;else if (c === ')') {
1103
+ depth--;
1104
+ if (depth === 0 && i !== type.length - 1) {
1105
+ balanced = false;
1106
+ break;
1107
+ }
1108
+ }
1109
+ }
1110
+ }
1111
+ if (balanced && depth === 0) {
1112
+ type = type.slice(1, -1).trim();
1113
+ changed = true;
1114
+ }
1115
+ }
1116
+ }
1117
+ return type;
1118
+ }
971
1119
  /**
972
1120
  * Parses `Name<A, B>` into name + raw arg strings, respecting nested brackets.
973
1121
  * Returns undefined when input isn't a generic reference.
@@ -1012,6 +1160,190 @@
1012
1160
  args: args
1013
1161
  };
1014
1162
  }
1163
+ /**
1164
+ * Parses `{[K in Iterable as NameType]?: Result}` mapped syntax.
1165
+ * Returns undefined when input isn't a mapped type.
1166
+ * @param {string} type - Trimmed type string starting with `{`.
1167
+ * @returns {object|undefined} Mapping struct with raw strings, or undefined.
1168
+ */
1169
+ function parseMappedRaw(type) {
1170
+ if (type[0] !== '{' || type[type.length - 1] !== '}') {
1171
+ return;
1172
+ }
1173
+ var inner = type.slice(1, -1).trim();
1174
+ var readonly;
1175
+ if (inner.startsWith('-readonly') && (inner[9] === ' ' || inner[9] === '[')) {
1176
+ readonly = '-';
1177
+ inner = inner.slice(9).trim();
1178
+ } else if (inner.startsWith('+readonly') && (inner[9] === ' ' || inner[9] === '[')) {
1179
+ readonly = '+';
1180
+ inner = inner.slice(9).trim();
1181
+ } else if (inner.startsWith('readonly') && (inner[8] === ' ' || inner[8] === '[')) {
1182
+ readonly = 'readonly';
1183
+ inner = inner.slice(8).trim();
1184
+ }
1185
+ if (inner[0] !== '[') {
1186
+ return;
1187
+ }
1188
+ // Find matching `]` for the opening `[`, quote-aware.
1189
+ var square = 0;
1190
+ var inSingle = false;
1191
+ var inDouble = false;
1192
+ var inBacktick = false;
1193
+ var close = -1;
1194
+ for (var i = 0; i < inner.length; i++) {
1195
+ var c = inner[i];
1196
+ var prev = i > 0 ? inner[i - 1] : '';
1197
+ if (c === "'" && !inDouble && !inBacktick && prev !== '\\') inSingle = !inSingle;else if (c === '"' && !inSingle && !inBacktick && prev !== '\\') inDouble = !inDouble;else if (c === '`' && !inSingle && !inDouble && prev !== '\\') inBacktick = !inBacktick;else if (!inSingle && !inDouble && !inBacktick) {
1198
+ if (c === '[') square++;else if (c === ']') {
1199
+ square--;
1200
+ if (square === 0) {
1201
+ close = i;
1202
+ break;
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+ if (close === -1) {
1208
+ return;
1209
+ }
1210
+ var bracket = inner.slice(1, close);
1211
+ var rest = inner.slice(close + 1).trim();
1212
+ var question;
1213
+ if (rest.startsWith('-?')) {
1214
+ question = '-';
1215
+ rest = rest.slice(2).trim();
1216
+ } else if (rest.startsWith('+?')) {
1217
+ question = '+';
1218
+ rest = rest.slice(2).trim();
1219
+ } else if (rest.startsWith('?')) {
1220
+ question = '?';
1221
+ rest = rest.slice(1).trim();
1222
+ }
1223
+ if (!rest.startsWith(':')) {
1224
+ return;
1225
+ }
1226
+ var resultRaw = rest.slice(1).trim();
1227
+ if (!resultRaw) {
1228
+ return;
1229
+ }
1230
+ var inIdx = findTopLevelKeyword(bracket, 'in');
1231
+ if (inIdx === -1) {
1232
+ return;
1233
+ }
1234
+ var element = bracket.slice(0, inIdx).trim();
1235
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(element)) {
1236
+ return;
1237
+ }
1238
+ var afterIn = bracket.slice(inIdx + 2).trim();
1239
+ if (!afterIn) {
1240
+ return;
1241
+ }
1242
+ var asIdx = findTopLevelKeyword(afterIn, 'as');
1243
+ var iterableRaw = afterIn;
1244
+ var nameRaw;
1245
+ if (asIdx !== -1) {
1246
+ iterableRaw = afterIn.slice(0, asIdx).trim();
1247
+ nameRaw = afterIn.slice(asIdx + 2).trim();
1248
+ if (!iterableRaw || !nameRaw) {
1249
+ return;
1250
+ }
1251
+ }
1252
+ return {
1253
+ element: element,
1254
+ iterableRaw: iterableRaw,
1255
+ nameRaw: nameRaw,
1256
+ resultRaw: resultRaw,
1257
+ question: question,
1258
+ readonly: readonly
1259
+ };
1260
+ }
1261
+ /**
1262
+ * Splits `Check extends Extends ? True : False` at top level.
1263
+ * Returns undefined when input isn't a conditional type.
1264
+ * @param {string} type - Trimmed type string.
1265
+ * @returns {{check: string, extends_: string, true_: string, false_: string}|undefined} Raw parts.
1266
+ */
1267
+ function parseConditionRaw(type) {
1268
+ var qIdx = findTopLevelChar(type, '?');
1269
+ if (qIdx === -1) {
1270
+ return;
1271
+ }
1272
+ var beforeQ = type.slice(0, qIdx);
1273
+ var afterQ = type.slice(qIdx + 1);
1274
+ var extIdx = findTopLevelKeyword(beforeQ, 'extends');
1275
+ if (extIdx === -1) {
1276
+ return;
1277
+ }
1278
+ var colonIdx = findTopLevelChar(afterQ, ':');
1279
+ if (colonIdx === -1) {
1280
+ return;
1281
+ }
1282
+ var check = beforeQ.slice(0, extIdx).trim();
1283
+ var extends_ = beforeQ.slice(extIdx + 7).trim();
1284
+ var true_ = afterQ.slice(0, colonIdx).trim();
1285
+ var false_ = afterQ.slice(colonIdx + 1).trim();
1286
+ if (!check || !extends_ || !true_ || !false_) {
1287
+ return;
1288
+ }
1289
+ // Guard against `?.` optional chaining and `??` nullish coalescing.
1290
+ if (type[qIdx + 1] === '.' || type[qIdx + 1] === '?') {
1291
+ return;
1292
+ }
1293
+ return {
1294
+ check: check,
1295
+ extends_: extends_,
1296
+ true_: true_,
1297
+ false_: false_
1298
+ };
1299
+ }
1300
+ /**
1301
+ * Splits `Object[Index]` at top level. Excludes tuples (`[...]`) and
1302
+ * empty-index arrays (`T[]`, handled earlier).
1303
+ * @param {string} type - Trimmed type string.
1304
+ * @returns {{objectRaw: string, indexRaw: string}|undefined} Raw parts.
1305
+ */
1306
+ function parseIndexedAccessRaw(type) {
1307
+ if (!type.endsWith(']') || type.endsWith('[]')) {
1308
+ return;
1309
+ }
1310
+ if (type[0] === '[') {
1311
+ return;
1312
+ }
1313
+ var openIdx = findTopLevelChar(type, '[');
1314
+ if (openIdx === -1) {
1315
+ return;
1316
+ }
1317
+ // Ensure the `[` at openIdx closes at the very end.
1318
+ var square = 0;
1319
+ var inSingle = false;
1320
+ var inDouble = false;
1321
+ var inBacktick = false;
1322
+ for (var i = openIdx; i < type.length; i++) {
1323
+ var c = type[i];
1324
+ var prev = i > 0 ? type[i - 1] : '';
1325
+ if (c === "'" && !inDouble && !inBacktick && prev !== '\\') inSingle = !inSingle;else if (c === '"' && !inSingle && !inBacktick && prev !== '\\') inDouble = !inDouble;else if (c === '`' && !inSingle && !inDouble && prev !== '\\') inBacktick = !inBacktick;else if (!inSingle && !inDouble && !inBacktick) {
1326
+ if (c === '[') square++;else if (c === ']') {
1327
+ square--;
1328
+ if (square === 0 && i !== type.length - 1) {
1329
+ return;
1330
+ }
1331
+ }
1332
+ }
1333
+ }
1334
+ if (square !== 0) {
1335
+ return;
1336
+ }
1337
+ var objectRaw = type.slice(0, openIdx).trim();
1338
+ var indexRaw = type.slice(openIdx + 1, -1).trim();
1339
+ if (!objectRaw || !indexRaw) {
1340
+ return;
1341
+ }
1342
+ return {
1343
+ objectRaw: objectRaw,
1344
+ indexRaw: indexRaw
1345
+ };
1346
+ }
1015
1347
  /**
1016
1348
  * 'DepFree' refers to the fact that this function has no dependencies,
1017
1349
  * while `expandType` depends on TypeScript itself for maximum compatibility.
@@ -1027,6 +1359,33 @@
1027
1359
  */
1028
1360
  function expandTypeDepFree(type) {
1029
1361
  type = type.trim();
1362
+ // '(123)' -> '123': strip balanced outer parens first so `(cond)?`
1363
+ // nullable and `(A|B)` unions see through them.
1364
+ var stripped = stripOuterParens(type);
1365
+ if (stripped !== type) {
1366
+ return expandTypeDepFree(stripped);
1367
+ }
1368
+ // `readonly T` erased at runtime, same shape as the inner type.
1369
+ if (type.startsWith('readonly ') && type.length > 9) {
1370
+ return expandTypeDepFree(type.slice(9).trim());
1371
+ }
1372
+ if (type === 'unique symbol') {
1373
+ return 'any';
1374
+ }
1375
+ // Conditionals before nullable: `A extends B ? C : D?` must keep the
1376
+ // nullable on the false branch, not lift it over the whole condition.
1377
+ // (`(cond)?` has no top-level `?` due to parens, so it falls through
1378
+ // to nullable below.)
1379
+ var earlyCond = parseConditionRaw(type);
1380
+ if (earlyCond) {
1381
+ return {
1382
+ type: 'condition',
1383
+ checkType: expandTypeDepFree(earlyCond.check),
1384
+ extendsType: expandTypeDepFree(earlyCond.extends_),
1385
+ trueType: expandTypeDepFree(earlyCond.true_),
1386
+ falseType: expandTypeDepFree(earlyCond.false_)
1387
+ };
1388
+ }
1030
1389
  // JSDocNullableType (`T?` / `?T`): union with null, matching expandType().
1031
1390
  if (type.endsWith('?') && type.length > 1) {
1032
1391
  return {
@@ -1040,20 +1399,9 @@
1040
1399
  members: [expandTypeDepFree(type.slice(1).trim()), 'null']
1041
1400
  };
1042
1401
  }
1043
- // `readonly T` erased at runtime, same shape as the inner type.
1044
- if (type.startsWith('readonly ') && type.length > 9) {
1045
- return expandTypeDepFree(type.slice(9).trim());
1046
- }
1047
- if (type === 'unique symbol') {
1048
- return 'any';
1049
- }
1050
- // '(123)' -> '123'
1051
- while (!type.includes('|') && type[0] === '(' && type[type.length - 1] === ')') {
1052
- type = type.slice(1, -1).trim();
1053
- }
1054
1402
  // (1) Rest parameters like ...string
1055
1403
  if (type[0] === '.' && type[1] === '.' && type[2] === '.') {
1056
- var elementType = type.slice(3);
1404
+ var elementType = expandTypeDepFree(type.slice(3).trim());
1057
1405
  return {
1058
1406
  type: 'array',
1059
1407
  elementType: elementType
@@ -1081,17 +1429,18 @@
1081
1429
  // (3) Object<...> or Record<...>
1082
1430
  if ((type.startsWith("Object<") || type.startsWith("Record<")) && type.endsWith('>')) {
1083
1431
  var recordSlice = type.slice(7, -1);
1084
- var firstComma = recordSlice.indexOf(',');
1085
- if (firstComma === -1) {
1432
+ var commaIdx = findTopLevelChar(recordSlice, ',');
1433
+ if (commaIdx === -1) {
1086
1434
  console.warn("expandTypeDepFree> invalid Object/Record");
1435
+ } else {
1436
+ var key = recordSlice.slice(0, commaIdx).trim();
1437
+ var val = recordSlice.slice(commaIdx + 1).trim();
1438
+ return {
1439
+ type: "record",
1440
+ key: expandTypeDepFree(key),
1441
+ val: expandTypeDepFree(val)
1442
+ };
1087
1443
  }
1088
- var key = recordSlice.slice(0, firstComma).trim();
1089
- var val = recordSlice.slice(firstComma + 1).trim();
1090
- return {
1091
- type: "record",
1092
- key: expandTypeDepFree(key),
1093
- val: expandTypeDepFree(val)
1094
- };
1095
1444
  }
1096
1445
  // (3b) Map<...> / Set<...' for dep-free parity with expandType()
1097
1446
  if (type.startsWith("Map<") && type.endsWith('>')) {
@@ -1124,40 +1473,102 @@
1124
1473
  args: args.map(expandTypeDepFree)
1125
1474
  };
1126
1475
  }
1127
- // (4) {...}
1476
+ // (4) Mapped types `{[K in X as Y]?: Z}` before the object-literal fallback.
1128
1477
  if (type[0] === '{' && type[type.length - 1] === '}') {
1129
- var propertiesArray = type.slice(1, -1).split(','); // ['entity: Entity', ' app: AppBase']
1478
+ var mapped = parseMappedRaw(type);
1479
+ if (mapped) {
1480
+ var out = {
1481
+ type: 'mapping',
1482
+ iterable: expandTypeDepFree(mapped.iterableRaw),
1483
+ element: mapped.element,
1484
+ result: expandTypeDepFree(mapped.resultRaw)
1485
+ };
1486
+ if (mapped.nameRaw !== undefined) {
1487
+ out.nameType = expandTypeDepFree(mapped.nameRaw);
1488
+ }
1489
+ if (mapped.question !== undefined) {
1490
+ out.question = mapped.question;
1491
+ }
1492
+ if (mapped.readonly !== undefined) {
1493
+ out.readonly = mapped.readonly;
1494
+ }
1495
+ return out;
1496
+ }
1497
+ var propertiesArray = splitTopLevel(type.slice(1, -1), ','); // ['entity: Entity', ' app: AppBase']
1130
1498
  var properties = {};
1131
- propertiesArray.forEach(function (_) {
1132
- var _$split$map = _.split(":").map(function (_) {
1133
- return _.trim();
1134
- }),
1135
- _$split$map2 = _slicedToArray(_$split$map, 2),
1136
- propName = _$split$map2[0],
1137
- propType = _$split$map2[1];
1138
- if (!propName || !propType) {
1139
- console.warn('expandTypeDepFree> unexpected type format, fix');
1140
- return false;
1499
+ var _iterator = _createForOfIteratorHelper(propertiesArray),
1500
+ _step;
1501
+ try {
1502
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1503
+ var entry = _step.value;
1504
+ var colonIdx = findTopLevelChar(entry, ':');
1505
+ if (colonIdx === -1) {
1506
+ // Empty `{}` yields one empty entry: the empty object type.
1507
+ if (entry.trim() === '') {
1508
+ continue;
1509
+ }
1510
+ console.warn('expandTypeDepFree> unexpected type format, fix');
1511
+ continue;
1512
+ }
1513
+ var propName = entry.slice(0, colonIdx).trim();
1514
+ var propTypeRaw = entry.slice(colonIdx + 1).trim();
1515
+ if (!propName || !propTypeRaw) {
1516
+ console.warn('expandTypeDepFree> unexpected type format, fix');
1517
+ continue;
1518
+ }
1519
+ properties[propName] = expandTypeDepFree(propTypeRaw);
1141
1520
  }
1142
- properties[propName] = propType;
1143
- });
1521
+ } catch (err) {
1522
+ _iterator.e(err);
1523
+ } finally {
1524
+ _iterator.f();
1525
+ }
1144
1526
  return {
1145
1527
  type: 'object',
1146
1528
  properties: properties
1147
1529
  };
1148
1530
  }
1149
- // (5) expand unions
1150
- var members = type.split("|");
1151
- if (members.length >= 2) {
1152
- members.forEach(function (_, i) {
1153
- return members[i] = _.trim();
1154
- });
1531
+ // (5) Unions and intersections via top-level splits (`&` binds tighter, so `|` first).
1532
+ // Note: conditionals already handled up front (before nullable) so `D?`
1533
+ // false branches keep their nullability; this slot is intentionally union-only.
1534
+ var unionParts = splitTopLevel(type, "|");
1535
+ if (unionParts.length >= 2) {
1155
1536
  return {
1156
1537
  type: 'union',
1157
- members: members.map(expandTypeDepFree)
1538
+ members: unionParts.map(function (_) {
1539
+ return expandTypeDepFree(_.trim());
1540
+ })
1541
+ };
1542
+ }
1543
+ var interParts = splitTopLevel(type, "&");
1544
+ if (interParts.length >= 2) {
1545
+ return {
1546
+ type: 'intersection',
1547
+ members: interParts.map(function (_) {
1548
+ return expandTypeDepFree(_.trim());
1549
+ })
1550
+ };
1551
+ }
1552
+ // (7) `keyof T` before typeof so `keyof typeof X` nests correctly.
1553
+ if (type.startsWith('keyof ') || type.startsWith('keyof(')) {
1554
+ var after = type.startsWith('keyof(') ? type.slice(5).trim() : type.slice(6).trim();
1555
+ if (after) {
1556
+ return {
1557
+ type: 'keyof',
1558
+ argument: expandTypeDepFree(after)
1559
+ };
1560
+ }
1561
+ }
1562
+ // (8) Indexed access `T[K]` (arrays `T[]` handled below, tuples above start with `[`).
1563
+ var indexed = parseIndexedAccessRaw(type);
1564
+ if (indexed) {
1565
+ return {
1566
+ type: 'indexedAccess',
1567
+ index: expandTypeDepFree(indexed.indexRaw),
1568
+ object: expandTypeDepFree(indexed.objectRaw)
1158
1569
  };
1159
1570
  }
1160
- // (6) expand [] Arrays
1571
+ // (9) expand [] Arrays
1161
1572
  // Test arrays: new pc.Mat3().set([1, 2, 3, "asd"])
1162
1573
  if (type.endsWith("[]")) {
1163
1574
  var _typeSlice2 = type.slice(0, -2);
@@ -1166,17 +1577,26 @@
1166
1577
  elementType: expandTypeDepFree(_typeSlice2)
1167
1578
  };
1168
1579
  }
1169
- // (7) expand tuples
1580
+ // (10) expand tuples
1170
1581
  if (type[0] === '[' && type[type.length - 1] === ']') {
1171
- var elements = type.slice(1, -1).split(','); // ['null', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture']
1582
+ var _inner2 = type.slice(1, -1);
1583
+ if (_inner2.trim() === '') {
1584
+ return {
1585
+ type: 'tuple',
1586
+ elements: []
1587
+ };
1588
+ }
1589
+ var elements = splitTopLevel(_inner2, ','); // ['null', ' Texture', ...]
1172
1590
  return {
1173
1591
  type: 'tuple',
1174
- elements: elements.map(expandTypeDepFree)
1592
+ elements: elements.map(function (_) {
1593
+ return expandTypeDepFree(_.trim());
1594
+ })
1175
1595
  };
1176
1596
  }
1177
- // (8) expand typeof expressions
1597
+ // (11) expand typeof expressions
1178
1598
  if (type.startsWith('typeof ')) {
1179
- var argument = expandTypeDepFree(type.substring(7));
1599
+ var argument = expandTypeDepFree(type.substring(7).trim());
1180
1600
  return {
1181
1601
  type: 'typeof',
1182
1602
  argument: argument
@@ -1202,79 +1622,6 @@
1202
1622
  return type;
1203
1623
  }
1204
1624
 
1205
- /**
1206
- * Infers a parameter type from its default value AST node.
1207
- * Returns widened types like TypeScript does (`= 0` means `number`, not
1208
- * literal `0`; `= null` widens to `any`).
1209
- * Returns `undefined` when nothing useful can be inferred — the caller then
1210
- * emits no check, exactly like an undocumented parameter today.
1211
- * Shapes match what `expandType` produces so they can be embedded as-is.
1212
- * @param {import('@babel/types').Node} node - The default value AST node.
1213
- * @returns {string | object | undefined} Inferred type or `undefined` to skip.
1214
- */
1215
- function inferTypeFromDefault$1(node) {
1216
- if (!node) {
1217
- return;
1218
- }
1219
- switch (node.type) {
1220
- case 'NumericLiteral':
1221
- return 'number';
1222
- case 'StringLiteral':
1223
- return 'string';
1224
- case 'BooleanLiteral':
1225
- return 'boolean';
1226
- case 'BigIntLiteral':
1227
- return {
1228
- type: 'bigint'
1229
- };
1230
- case 'RegExpLiteral':
1231
- return 'RegExp';
1232
- case 'TemplateLiteral':
1233
- return 'string';
1234
- case 'ArrayExpression':
1235
- return {
1236
- type: 'array',
1237
- elementType: 'any'
1238
- };
1239
- case 'ObjectExpression':
1240
- return {
1241
- type: 'object',
1242
- properties: {}
1243
- };
1244
- case 'ArrowFunctionExpression':
1245
- case 'FunctionExpression':
1246
- return 'Function';
1247
- case 'NewExpression':
1248
- {
1249
- var callee = node.callee;
1250
- if (callee.type === 'Identifier') {
1251
- return callee.name;
1252
- }
1253
- break;
1254
- }
1255
- case 'UnaryExpression':
1256
- {
1257
- var operator = node.operator,
1258
- argument = node.argument;
1259
- if (operator === '!') {
1260
- return 'boolean';
1261
- }
1262
- if (operator === 'void' || operator === 'typeof') {
1263
- return operator === 'void' ? 'undefined' : 'string';
1264
- }
1265
- if ((operator === '-' || operator === '+') && argument.type === 'NumericLiteral') {
1266
- return 'number';
1267
- }
1268
- if ((operator === '-' || operator === '+') && argument.type === 'BigIntLiteral') {
1269
- return {
1270
- type: 'bigint'
1271
- };
1272
- }
1273
- break;
1274
- }
1275
- }
1276
- }
1277
-
1278
1625
  /**
1279
1626
  * Extracts the parameter name and its optionality from a JSDoc parameter string.
1280
1627
  *
@@ -1339,73 +1686,863 @@
1339
1686
  *
1340
1687
  * It iterates through the lines of a `CommentBlock` from the Babel AST, looking for `@typedef` and `@property`
1341
1688
  * annotations. When it finds a typedef, it stores it in the `typedefs` record. When it finds a property,
1342
- * it adds it to the last found typedef if it is an object type.
1689
+ * it adds it to the last found typedef if it is an object type. `@template` names preceding a
1690
+ * typedef are recorded in `typedefTemplates` so generic references can instantiate.
1343
1691
  * @param {Record<string, object>} typedefs - An object to store typedefs, mapping type names to their expanded definitions.
1692
+ * @param {Record<string, string[]>} typedefTemplates - An object to store template parameter names per generic typedef.
1344
1693
  * @param {Console["warn"]} warn - A warn function used for emitting warnings about non-extensible types.
1345
1694
  * @param {import("@babel/types").Comment} comment - A comment extracted from Babel's AST, expected to be a CommentBlock containing type definitions.
1346
1695
  * @param {Function} expandType - A function that takes a type expression as a string and returns a structured representation of the type.
1347
1696
  */
1348
- function parseJSDocTypedef(typedefs, warn, comment, expandType) {
1349
- var type = comment.type,
1350
- value = comment.value;
1351
- if (type !== 'CommentBlock') {
1697
+ function parseJSDocTypedef(typedefs, typedefTemplates, warn, comment, expandType) {
1698
+ var type = comment.type,
1699
+ value = comment.value;
1700
+ if (type !== 'CommentBlock') {
1701
+ return;
1702
+ }
1703
+ var lines = value.split('\n');
1704
+ var lastTypedef;
1705
+ var pendingTemplates = [];
1706
+ /**
1707
+ * @param {string} line - The trimmed JSDoc line.
1708
+ * @returns {boolean} True when the line held a template tag.
1709
+ */
1710
+ function harvestTemplate(line) {
1711
+ var match = line.match(/@template \{(.*?)\} ([a-zA-Z0-9_$]+)/);
1712
+ if (match) {
1713
+ pendingTemplates.push(match[2]);
1714
+ return true;
1715
+ }
1716
+ match = line.match(/@template (?:\{.*?\} )?\[([a-zA-Z0-9_$]+)=/);
1717
+ if (match) {
1718
+ pendingTemplates.push(match[1]);
1719
+ return true;
1720
+ }
1721
+ match = line.match(/@template ([a-zA-Z0-9_$]+)(?![a-zA-Z0-9_$])/);
1722
+ if (match) {
1723
+ pendingTemplates.push(match[1]);
1724
+ return true;
1725
+ }
1726
+ return false;
1727
+ }
1728
+ var _iterator = _createForOfIteratorHelper(lines),
1729
+ _step;
1730
+ try {
1731
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1732
+ var line = _step.value;
1733
+ line = line.trim();
1734
+ if (line[0] === '*') {
1735
+ line = line.slice(1).trim();
1736
+ }
1737
+ if (line.startsWith('@template')) {
1738
+ harvestTemplate(line);
1739
+ } else if (line.startsWith('@typedef')) {
1740
+ var _extractCurlyContent = extractCurlyContent(line),
1741
+ def = _extractCurlyContent.content,
1742
+ nextIndex = _extractCurlyContent.nextIndex;
1743
+ var name = line.substring(nextIndex).trim();
1744
+ // Drop description
1745
+ name = name.split(' ')[0];
1746
+ lastTypedef = expandType(def);
1747
+ // Ignore @typedef's that only refer to themselves in another file (see typedef-overwrite test)
1748
+ if (lastTypedef !== name) {
1749
+ typedefs[name] = lastTypedef;
1750
+ if (pendingTemplates.length) {
1751
+ typedefTemplates[name] = _toConsumableArray(pendingTemplates);
1752
+ }
1753
+ }
1754
+ pendingTemplates = [];
1755
+ } else if (line.startsWith('@property')) {
1756
+ var _lastTypedef;
1757
+ // class @property
1758
+ if (!lastTypedef) {
1759
+ continue;
1760
+ }
1761
+ var _extractCurlyContent2 = extractCurlyContent(line),
1762
+ content = _extractCurlyContent2.content,
1763
+ _nextIndex = _extractCurlyContent2.nextIndex;
1764
+ var rest = line.substring(_nextIndex);
1765
+ var propType = expandType(content);
1766
+ var _extractNameAndOption = extractNameAndOptionality(rest),
1767
+ _extractNameAndOption2 = _slicedToArray(_extractNameAndOption, 2),
1768
+ _name = _extractNameAndOption2[0],
1769
+ optional = _extractNameAndOption2[1];
1770
+ // console.log({name, optional, propType});
1771
+ var finalType = annotateOptional(propType, optional);
1772
+ if (((_lastTypedef = lastTypedef) === null || _lastTypedef === void 0 ? void 0 : _lastTypedef.type) === 'object') {
1773
+ lastTypedef.properties[_name] = finalType;
1774
+ } else {
1775
+ warn("not an extensible type", lastTypedef);
1776
+ }
1777
+ } else if (line.startsWith('@callback')) {
1778
+ var _name2 = line.substring(9).trim();
1779
+ typedefs[_name2] = 'Function';
1780
+ }
1781
+ }
1782
+ } catch (err) {
1783
+ _iterator.e(err);
1784
+ } finally {
1785
+ _iterator.f();
1786
+ }
1787
+ }
1788
+
1789
+ /**
1790
+ * @typedef {ReturnType<typeof parseJSDoc>} ParseJSDocReturnType
1791
+ */
1792
+ /**
1793
+ * @typedef {typeof expandTypeDepFree} ExpandType
1794
+ * @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
1795
+ */
1796
+ /**
1797
+ * Parses JSDoc comments to extract parameter type information.
1798
+ *
1799
+ * @param {string} src - The JSDoc comment string to parse.
1800
+ * @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
1801
+ * @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
1802
+ */
1803
+ function parseJSDoc(src) {
1804
+ var expandType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : expandTypeDepFree;
1805
+ // Parse something like: @param {Object} [kwargs={}] Optional arguments.
1806
+ var regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\-\{\}\.'" ]+)/g;
1807
+ var matches = _toConsumableArray(src.matchAll(regex));
1808
+ /** @type {Record<string, ExpandTypeReturnType>} */
1809
+ var params = Object.create(null);
1810
+ matches.forEach(function (_) {
1811
+ var type = expandType(_[1].trim());
1812
+ var name = _[2].trim();
1813
+ var optional = false;
1814
+ // Examples:
1815
+ // name: [kwargs={}] The configuration parameters.
1816
+ // name: [d = 1.0] Sample spacing
1817
+ if (name[0] === '[') {
1818
+ // Counting opening/closing brackets for perfect match
1819
+ var openCloseCount = 1;
1820
+ var i = 1;
1821
+ for (; i < name.length; i++) {
1822
+ var c = name[i];
1823
+ if (c === '[') {
1824
+ openCloseCount++;
1825
+ } else if (c === ']') {
1826
+ openCloseCount--;
1827
+ }
1828
+ if (openCloseCount === 0) {
1829
+ break;
1830
+ }
1831
+ }
1832
+ // Afterwards name will be: d = 1.0
1833
+ name = name.substring(1, i);
1834
+ // mark it for the type:
1835
+ optional = true;
1836
+ }
1837
+ // Strip the rest (either leftover of optional value or description)
1838
+ name = name.split(' ')[0].split('=')[0].trim();
1839
+ var annotatedType = annotateOptional(type, optional);
1840
+ // Turn "options.stats[].unitsName" into ['options', 'stats', 'unitsName'].
1841
+ var parts = name.split(/[\[\]]*\./);
1842
+ var properties = params;
1843
+ var _iterator = _createForOfIteratorHelper(parts),
1844
+ _step;
1845
+ try {
1846
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1847
+ var part = _step.value;
1848
+ var toptype = properties[part];
1849
+ if (!toptype) {
1850
+ // No toptype means we resolved as far as possible, now we can add `annotatedType`.
1851
+ console.assert(part === parts.at(-1), 'Current part and last part should be the same.');
1852
+ properties[part] = annotatedType;
1853
+ } else if (toptype.type === "union") {
1854
+ var typeObject = toptype.members.find(function (_) {
1855
+ return (_ === null || _ === void 0 ? void 0 : _.type) === 'object';
1856
+ });
1857
+ properties = typeObject.properties;
1858
+ } else if (toptype.type === "array") {
1859
+ properties = toptype.elementType.properties;
1860
+ } else if (toptype.type === "object") {
1861
+ toptype.properties = toptype.properties || Object.create(null);
1862
+ properties = toptype.properties;
1863
+ } else {
1864
+ console.warn("parseJSDoc> Skipping @param, unseen syntax detected. Please check if your JSDoc is valid or open an issue about this!", {
1865
+ src: src,
1866
+ toptype: toptype,
1867
+ parts: parts,
1868
+ annotatedType: annotatedType
1869
+ });
1870
+ }
1871
+ }
1872
+ } catch (err) {
1873
+ _iterator.e(err);
1874
+ } finally {
1875
+ _iterator.f();
1876
+ }
1877
+ });
1878
+ if (Object.keys(params).length === 0) {
1879
+ return;
1880
+ }
1881
+ return params;
1882
+ }
1883
+
1884
+ /**
1885
+ * Infers a parameter type from its default value AST node.
1886
+ * Returns widened types like TypeScript does (`= 0` means `number`, not
1887
+ * literal `0`; `= null` widens to `any`).
1888
+ * Returns `undefined` when nothing useful can be inferred — the caller then
1889
+ * emits no check, exactly like an undocumented parameter today.
1890
+ * Shapes match what `expandType` produces so they can be embedded as-is.
1891
+ * @param {import('@babel/types').Node} node - The default value AST node.
1892
+ * @returns {string | object | undefined} Inferred type or `undefined` to skip.
1893
+ */
1894
+ function inferTypeFromDefault$1(node) {
1895
+ if (!node) {
1896
+ return;
1897
+ }
1898
+ switch (node.type) {
1899
+ case 'NumericLiteral':
1900
+ return 'number';
1901
+ case 'StringLiteral':
1902
+ return 'string';
1903
+ case 'BooleanLiteral':
1904
+ return 'boolean';
1905
+ case 'BigIntLiteral':
1906
+ return {
1907
+ type: 'bigint'
1908
+ };
1909
+ case 'RegExpLiteral':
1910
+ return 'RegExp';
1911
+ case 'TemplateLiteral':
1912
+ return 'string';
1913
+ case 'ArrayExpression':
1914
+ return {
1915
+ type: 'array',
1916
+ elementType: 'any'
1917
+ };
1918
+ case 'ObjectExpression':
1919
+ return {
1920
+ type: 'object',
1921
+ properties: {}
1922
+ };
1923
+ case 'ArrowFunctionExpression':
1924
+ case 'FunctionExpression':
1925
+ return 'Function';
1926
+ case 'NewExpression':
1927
+ {
1928
+ var callee = node.callee;
1929
+ if (callee.type === 'Identifier') {
1930
+ return callee.name;
1931
+ }
1932
+ break;
1933
+ }
1934
+ case 'UnaryExpression':
1935
+ {
1936
+ var operator = node.operator,
1937
+ argument = node.argument;
1938
+ if (operator === '!') {
1939
+ return 'boolean';
1940
+ }
1941
+ if (operator === 'void' || operator === 'typeof') {
1942
+ return operator === 'void' ? 'undefined' : 'string';
1943
+ }
1944
+ if ((operator === '-' || operator === '+') && argument.type === 'NumericLiteral') {
1945
+ return 'number';
1946
+ }
1947
+ if ((operator === '-' || operator === '+') && argument.type === 'BigIntLiteral') {
1948
+ return {
1949
+ type: 'bigint'
1950
+ };
1951
+ }
1952
+ break;
1953
+ }
1954
+ }
1955
+ }
1956
+
1957
+ // Declaration-site ranks: the field always beats constructor assignments,
1958
+ // like TSC where the declared type wins over assigned values.
1959
+ var FIELD_JSDOC = 3;
1960
+ var CTOR_JSDOC = 2;
1961
+ var FIELD_INFER = 1;
1962
+ var CTOR_INFER = 0;
1963
+ /**
1964
+ * Reads the last block comment attached to a node.
1965
+ * @param {*} node - Babel AST node.
1966
+ * @returns {string|undefined} Comment text or undefined.
1967
+ */
1968
+ function lastBlockComment(node) {
1969
+ var list = node === null || node === void 0 ? void 0 : node.leadingComments;
1970
+ if (!list) {
1971
+ return;
1972
+ }
1973
+ for (var i = list.length - 1; i >= 0; i--) {
1974
+ if (list[i].type === 'CommentBlock') {
1975
+ return list[i].value;
1976
+ }
1977
+ }
1978
+ }
1979
+ /**
1980
+ * Extracts a `{...}`-typed JSDoc tag (`@type`, `@returns`) from a comment.
1981
+ * @param {string|undefined} comment - Comment text.
1982
+ * @param {string} tag - Tag name including `@`.
1983
+ * @param {Function} expandType - String type to structured type.
1984
+ * @returns {{type: *, raw: string}|undefined} Expanded type plus raw text.
1985
+ */
1986
+ function tagType(comment, tag, expandType) {
1987
+ if (!comment) {
1988
+ return;
1989
+ }
1990
+ var idx = comment.search(new RegExp("".concat(tag, "(?=[\\s{])")));
1991
+ if (idx === -1) {
1992
+ return;
1993
+ }
1994
+ var slice = comment.slice(idx);
1995
+ if (!slice.includes('{')) {
1996
+ return;
1997
+ }
1998
+ try {
1999
+ var _extractCurlyContent = extractCurlyContent(slice),
2000
+ content = _extractCurlyContent.content;
2001
+ if (!content || !content.trim()) {
2002
+ return;
2003
+ }
2004
+ return {
2005
+ type: expandType(content.trim()),
2006
+ raw: content.trim()
2007
+ };
2008
+ } catch (_unused) {
2009
+ // Unparseable annotation: fail open, harvest continues.
2010
+ }
2011
+ }
2012
+ /**
2013
+ * Reads a static property name: identifiers and string literals, including
2014
+ * computed `['name']` forms. Anything dynamic yields undefined.
2015
+ * @param {*} key - Babel key node.
2016
+ * @param {boolean} computed - Whether the key position is computed.
2017
+ * @returns {string|undefined} Property name or undefined.
2018
+ */
2019
+ function keyName(key, computed) {
2020
+ if (!key) {
2021
+ return;
2022
+ }
2023
+ if (key.type === 'Identifier' && !computed) {
2024
+ return key.name;
2025
+ }
2026
+ if (key.type === 'StringLiteral') {
2027
+ return key.value;
2028
+ }
2029
+ }
2030
+ /**
2031
+ * Unwraps parenthesized and TS `as`/`satisfies` nodes.
2032
+ * @param {*} node - Babel AST node.
2033
+ * @returns {*} Unwrapped node.
2034
+ */
2035
+ function unwrap(node) {
2036
+ while (node && (node.type === 'ParenthesizedExpression' || node.type === 'TSAsExpression' || node.type === 'TSSatisfiesExpression')) {
2037
+ node = node.expression;
2038
+ }
2039
+ return node;
2040
+ }
2041
+ /**
2042
+ * Merges a harvested entry: higher rank wins the type, flags accumulate
2043
+ * (`optional` reflects runtime reality, `@readonly` anywhere counts).
2044
+ * Two disagreeing humans (JSDoc vs JSDoc) warn, like a TSC error.
2045
+ * @param {Record<string, object>} fields - Collected entries by name.
2046
+ * @param {string} name - Property name.
2047
+ * @param {object} entry - Entry with type, rank, raw, readonly, optional.
2048
+ * @param {Function} warn - Transpile-time warning function.
2049
+ * @param {string} className - Class name for messages.
2050
+ */
2051
+ function record(fields, name, entry, warn, className) {
2052
+ var existing = fields[name];
2053
+ if (!existing) {
2054
+ fields[name] = entry;
2055
+ return;
2056
+ }
2057
+ if (entry.jsdoc && existing.jsdoc && entry.raw !== existing.raw) {
2058
+ warn("harvestClassShape: ".concat(className, ".").concat(name, " has conflicting JSDoc types (").concat(existing.raw, " vs ").concat(entry.raw, ")"));
2059
+ }
2060
+ var winner = entry.rank >= existing.rank ? entry : existing;
2061
+ winner.readonly = existing.readonly || entry.readonly;
2062
+ winner.optional = existing.optional || entry.optional;
2063
+ fields[name] = winner;
2064
+ }
2065
+ /**
2066
+ * Turns a collected entry into a type struct, applying flags.
2067
+ * @param {object} entry - Collected entry.
2068
+ * @returns {*} Type struct or name.
2069
+ */
2070
+ function entryToType(entry) {
2071
+ var _entry$type;
2072
+ var t = (_entry$type = entry.type) !== null && _entry$type !== void 0 ? _entry$type : 'any';
2073
+ if (!entry.readonly && !entry.optional) {
2074
+ return t;
2075
+ }
2076
+ if (t && _typeof(t) === 'object') {
2077
+ return _objectSpread2(_objectSpread2(_objectSpread2({}, t), entry.readonly ? {
2078
+ readonly: true
2079
+ } : {}), entry.optional ? {
2080
+ optional: true
2081
+ } : {});
2082
+ }
2083
+ var out = {
2084
+ type: t
2085
+ };
2086
+ if (entry.readonly) {
2087
+ out.readonly = true;
2088
+ }
2089
+ if (entry.optional) {
2090
+ out.optional = true;
2091
+ }
2092
+ return out;
2093
+ }
2094
+ /**
2095
+ * Records `this.x = ...` (or `+=`, `++`) assignments.
2096
+ * @param {*} left - Assignment target.
2097
+ * @param {*} right - Assigned value or undefined for op-assign/update.
2098
+ * @param {string|undefined} comment - Leading comment text.
2099
+ * @param {boolean} conditional - True inside conditionals (counts as optional).
2100
+ * @param {object} ctx - Harvest context with fields, expandType, warn, className.
2101
+ */
2102
+ function recordThisAssign(left, right, comment, conditional, ctx) {
2103
+ var _declared$type;
2104
+ var fields = ctx.fields,
2105
+ expandType = ctx.expandType,
2106
+ warn = ctx.warn,
2107
+ className = ctx.className;
2108
+ left = unwrap(left);
2109
+ if (!left || left.type !== 'MemberExpression' || left.computed) {
2110
+ return;
2111
+ }
2112
+ if (!left.object || left.object.type !== 'ThisExpression') {
2113
+ return;
2114
+ }
2115
+ var name = keyName(left.property, false);
2116
+ if (name === undefined) {
2117
+ return;
2118
+ }
2119
+ var declared = tagType(comment, '@type', expandType);
2120
+ if (right === undefined) {
2121
+ // Op-assign/update (`+=`, `++`): exists, type unknown.
2122
+ record(fields, name, {
2123
+ type: 'any',
2124
+ rank: CTOR_INFER,
2125
+ jsdoc: false,
2126
+ optional: conditional
2127
+ }, warn, className);
2128
+ return;
2129
+ }
2130
+ var inferred = inferTypeFromDefault$1(unwrap(right));
2131
+ var type = (_declared$type = declared === null || declared === void 0 ? void 0 : declared.type) !== null && _declared$type !== void 0 ? _declared$type : inferred;
2132
+ if (type === undefined) {
2133
+ return;
2134
+ }
2135
+ record(fields, name, {
2136
+ type: type,
2137
+ rank: declared ? CTOR_JSDOC : CTOR_INFER,
2138
+ jsdoc: declared !== undefined,
2139
+ raw: declared === null || declared === void 0 ? void 0 : declared.raw,
2140
+ optional: conditional
2141
+ }, warn, className);
2142
+ }
2143
+ /**
2144
+ * Handles `Object.assign(this, {...})` with inline object literals.
2145
+ * @param {*} node - CallExpression node.
2146
+ * @param {boolean} conditional - True inside conditionals.
2147
+ * @param {object} ctx - Harvest context.
2148
+ * @returns {boolean} True when the call was `Object.assign` on `this`.
2149
+ */
2150
+ function recordObjectAssign(node, conditional, ctx) {
2151
+ var _callee$object, _callee$property;
2152
+ var fields = ctx.fields,
2153
+ expandType = ctx.expandType,
2154
+ warn = ctx.warn,
2155
+ className = ctx.className;
2156
+ var callee = node.callee,
2157
+ args = node.arguments;
2158
+ if (!callee || callee.type !== 'MemberExpression' || callee.computed) {
2159
+ return false;
2160
+ }
2161
+ if (((_callee$object = callee.object) === null || _callee$object === void 0 ? void 0 : _callee$object.type) !== 'Identifier' || callee.object.name !== 'Object') {
2162
+ return false;
2163
+ }
2164
+ if (((_callee$property = callee.property) === null || _callee$property === void 0 ? void 0 : _callee$property.type) !== 'Identifier' || callee.property.name !== 'assign') {
2165
+ return false;
2166
+ }
2167
+ if (!args.length || args[0].type !== 'ThisExpression') {
2168
+ return false;
2169
+ }
2170
+ var _iterator = _createForOfIteratorHelper(args.slice(1)),
2171
+ _step;
2172
+ try {
2173
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2174
+ var arg = _step.value;
2175
+ if (!arg || arg.type !== 'ObjectExpression') {
2176
+ continue;
2177
+ }
2178
+ var _iterator2 = _createForOfIteratorHelper(arg.properties),
2179
+ _step2;
2180
+ try {
2181
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2182
+ var _declared$type2;
2183
+ var prop = _step2.value;
2184
+ if (!prop || prop.type !== 'ObjectProperty') {
2185
+ continue;
2186
+ }
2187
+ var name = keyName(prop.key, prop.computed);
2188
+ if (name === undefined) {
2189
+ continue;
2190
+ }
2191
+ var comment = lastBlockComment(prop);
2192
+ var declared = tagType(comment, '@type', expandType);
2193
+ var type = (_declared$type2 = declared === null || declared === void 0 ? void 0 : declared.type) !== null && _declared$type2 !== void 0 ? _declared$type2 : inferTypeFromDefault$1(unwrap(prop.value));
2194
+ if (type === undefined) {
2195
+ continue;
2196
+ }
2197
+ record(fields, name, {
2198
+ type: type,
2199
+ rank: declared ? CTOR_JSDOC : CTOR_INFER,
2200
+ jsdoc: declared !== undefined,
2201
+ raw: declared === null || declared === void 0 ? void 0 : declared.raw,
2202
+ optional: conditional
2203
+ }, warn, className);
2204
+ }
2205
+ } catch (err) {
2206
+ _iterator2.e(err);
2207
+ } finally {
2208
+ _iterator2.f();
2209
+ }
2210
+ }
2211
+ } catch (err) {
2212
+ _iterator.e(err);
2213
+ } finally {
2214
+ _iterator.f();
2215
+ }
2216
+ return true;
2217
+ }
2218
+ /**
2219
+ * Walks an expression for `this.x` writes. Stops at function boundaries:
2220
+ * deferred writes (callbacks) are out of scope.
2221
+ * @param {*} expr - Babel expression node.
2222
+ * @param {boolean} conditional - True inside conditionals.
2223
+ * @param {object} ctx - Harvest context.
2224
+ * @param {*} stmt - Enclosing statement carrying leading comments.
2225
+ */
2226
+ function walkExpression(expr, conditional, ctx, stmt) {
2227
+ if (!expr) {
2228
+ return;
2229
+ }
2230
+ switch (expr.type) {
2231
+ case 'AssignmentExpression':
2232
+ {
2233
+ var comment = lastBlockComment(stmt);
2234
+ if (expr.operator === '=') {
2235
+ recordThisAssign(expr.left, expr.right, comment, conditional, ctx);
2236
+ walkExpression(expr.right, conditional, ctx, stmt);
2237
+ } else {
2238
+ recordThisAssign(expr.left, undefined, comment, conditional, ctx);
2239
+ }
2240
+ break;
2241
+ }
2242
+ case 'UpdateExpression':
2243
+ recordThisAssign(expr.argument, undefined, lastBlockComment(stmt), conditional, ctx);
2244
+ break;
2245
+ case 'SequenceExpression':
2246
+ var _iterator3 = _createForOfIteratorHelper(expr.expressions),
2247
+ _step3;
2248
+ try {
2249
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2250
+ var each = _step3.value;
2251
+ walkExpression(each, conditional, ctx, stmt);
2252
+ }
2253
+ } catch (err) {
2254
+ _iterator3.e(err);
2255
+ } finally {
2256
+ _iterator3.f();
2257
+ }
2258
+ break;
2259
+ case 'LogicalExpression':
2260
+ walkExpression(expr.right, true, ctx, stmt);
2261
+ break;
2262
+ case 'ConditionalExpression':
2263
+ walkExpression(expr.consequent, true, ctx, stmt);
2264
+ walkExpression(expr.alternate, true, ctx, stmt);
2265
+ break;
2266
+ case 'CallExpression':
2267
+ recordObjectAssign(expr, conditional, ctx);
2268
+ break;
2269
+ }
2270
+ }
2271
+ /**
2272
+ * Walks a statement for `this.x` writes. Conditional wrappers mark entries
2273
+ * optional; nested functions are boundaries.
2274
+ * @param {*} st - Babel statement node.
2275
+ * @param {boolean} conditional - True inside conditionals.
2276
+ * @param {object} ctx - Harvest context.
2277
+ */
2278
+ function walkStatement(st, conditional, ctx) {
2279
+ if (!st) {
2280
+ return;
2281
+ }
2282
+ switch (st.type) {
2283
+ case 'BlockStatement':
2284
+ var _iterator4 = _createForOfIteratorHelper(st.body),
2285
+ _step4;
2286
+ try {
2287
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
2288
+ var each = _step4.value;
2289
+ walkStatement(each, conditional, ctx);
2290
+ }
2291
+ } catch (err) {
2292
+ _iterator4.e(err);
2293
+ } finally {
2294
+ _iterator4.f();
2295
+ }
2296
+ break;
2297
+ case 'ExpressionStatement':
2298
+ walkExpression(st.expression, conditional, ctx, st);
2299
+ break;
2300
+ case 'ReturnStatement':
2301
+ walkExpression(st.argument, conditional, ctx, st);
2302
+ break;
2303
+ case 'IfStatement':
2304
+ walkStatement(st.consequent, true, ctx);
2305
+ walkStatement(st.alternate, true, ctx);
2306
+ break;
2307
+ case 'WhileStatement':
2308
+ case 'ForStatement':
2309
+ case 'ForInStatement':
2310
+ case 'ForOfStatement':
2311
+ walkStatement(st.body, true, ctx);
2312
+ break;
2313
+ case 'DoWhileStatement':
2314
+ walkStatement(st.body, conditional, ctx);
2315
+ break;
2316
+ case 'SwitchStatement':
2317
+ var _iterator5 = _createForOfIteratorHelper(st.cases),
2318
+ _step5;
2319
+ try {
2320
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
2321
+ var _each = _step5.value;
2322
+ var _iterator6 = _createForOfIteratorHelper(_each.consequent),
2323
+ _step6;
2324
+ try {
2325
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
2326
+ var consequent = _step6.value;
2327
+ walkStatement(consequent, true, ctx);
2328
+ }
2329
+ } catch (err) {
2330
+ _iterator6.e(err);
2331
+ } finally {
2332
+ _iterator6.f();
2333
+ }
2334
+ }
2335
+ } catch (err) {
2336
+ _iterator5.e(err);
2337
+ } finally {
2338
+ _iterator5.f();
2339
+ }
2340
+ break;
2341
+ case 'TryStatement':
2342
+ walkStatement(st.block, conditional, ctx);
2343
+ if (st.handler) {
2344
+ walkStatement(st.handler.body, true, ctx);
2345
+ }
2346
+ if (st.finalizer) {
2347
+ walkStatement(st.finalizer, conditional, ctx);
2348
+ }
2349
+ break;
2350
+ case 'LabeledStatement':
2351
+ walkStatement(st.body, conditional, ctx);
2352
+ break;
2353
+ }
2354
+ }
2355
+ /**
2356
+ * Reads the single parameter name of a setter.
2357
+ * @param {*} param - Babel parameter node.
2358
+ * @returns {string|undefined} Name or undefined.
2359
+ */
2360
+ function setterParamName(param) {
2361
+ if (!param) {
2362
+ return;
2363
+ }
2364
+ if (param.type === 'Identifier') {
2365
+ return param.name;
2366
+ }
2367
+ if (param.type === 'AssignmentPattern' && param.left.type === 'Identifier') {
2368
+ return param.left.name;
2369
+ }
2370
+ }
2371
+ /**
2372
+ * Reads a setter's value type: its `@param` JSDoc first, `@type` fallback.
2373
+ * @param {*} set - Babel ClassMethod (kind set) node.
2374
+ * @param {Function} expandType - String type to structured type.
2375
+ * @returns {{type: *, raw: string|undefined, jsdoc: boolean}} Type plus metadata.
2376
+ */
2377
+ function setterType(set, expandType) {
2378
+ var setComment = lastBlockComment(set);
2379
+ var paramName = setterParamName(set.params[0]);
2380
+ var params = parseJSDoc(setComment !== null && setComment !== void 0 ? setComment : '', expandType);
2381
+ if (paramName && params && params[paramName] !== undefined) {
2382
+ return {
2383
+ type: params[paramName],
2384
+ raw: undefined,
2385
+ jsdoc: true
2386
+ };
2387
+ }
2388
+ var tagged = tagType(setComment, '@type', expandType);
2389
+ if (tagged) {
2390
+ return {
2391
+ type: tagged.type,
2392
+ raw: tagged.raw,
2393
+ jsdoc: true
2394
+ };
2395
+ }
2396
+ return {
2397
+ type: 'any',
2398
+ raw: undefined,
2399
+ jsdoc: false
2400
+ };
2401
+ }
2402
+ /**
2403
+ * Harvests the instance shape of a class: field declarations (JSDoc wins,
2404
+ * else inferred), constructor `this.x` writes (field site wins conflicts),
2405
+ * methods as `Function`, getter/setter pairs as writable and getter-only as
2406
+ * readonly. Statics, privates and dynamic keys are skipped.
2407
+ * @param {*} node - Babel ClassDeclaration node.
2408
+ * @param {object} opts - Options with expandType and warn.
2409
+ * @param {Function} opts.expandType - String type to structured type.
2410
+ * @param {Function} opts.warn - Transpile-time warning function.
2411
+ * @returns {{name: string, shape: object}|undefined} Class name plus object shape.
2412
+ */
2413
+ function harvestClassShape(node, _ref) {
2414
+ var expandType = _ref.expandType,
2415
+ warn = _ref.warn;
2416
+ var id = node === null || node === void 0 ? void 0 : node.id;
2417
+ if (!id || id.type !== 'Identifier' || !id.name) {
1352
2418
  return;
1353
2419
  }
1354
- var lines = value.split('\n');
1355
- var lastTypedef;
1356
- var _iterator = _createForOfIteratorHelper(lines),
1357
- _step;
2420
+ var className = id.name;
2421
+ var ctx = {
2422
+ fields: {},
2423
+ gets: {},
2424
+ sets: {},
2425
+ expandType: expandType,
2426
+ warn: warn,
2427
+ className: className
2428
+ };
2429
+ var ctor = null;
2430
+ var _iterator7 = _createForOfIteratorHelper(node.body.body),
2431
+ _step7;
1358
2432
  try {
1359
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
1360
- var line = _step.value;
1361
- line = line.trim();
1362
- if (line[0] === '*') {
1363
- line = line.slice(1).trim();
2433
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
2434
+ var el = _step7.value;
2435
+ if (el.type === 'ClassPrivateProperty' || el.type === 'ClassPrivateMethod') {
2436
+ continue;
1364
2437
  }
1365
- if (line.startsWith('@typedef')) {
1366
- var _extractCurlyContent = extractCurlyContent(line),
1367
- def = _extractCurlyContent.content,
1368
- nextIndex = _extractCurlyContent.nextIndex;
1369
- var name = line.substring(nextIndex).trim();
1370
- // Drop description
1371
- name = name.split(' ')[0];
1372
- lastTypedef = expandType(def);
1373
- // Ignore @typedef's that only refer to themselves in another file (see typedef-overwrite test)
1374
- if (lastTypedef !== name) {
1375
- typedefs[name] = lastTypedef;
2438
+ var _name3 = keyName(el.key, el.computed);
2439
+ if (_name3 === undefined) {
2440
+ continue;
2441
+ }
2442
+ if (el.type === 'ClassProperty' || el.type === 'PropertyDefinition') {
2443
+ var _declared$type3;
2444
+ if (el.static || el.declare) {
2445
+ continue;
1376
2446
  }
1377
- } else if (line.startsWith('@property')) {
1378
- var _lastTypedef;
1379
- // class @property
1380
- if (!lastTypedef) {
2447
+ var comment = lastBlockComment(el);
2448
+ var declared = tagType(comment, '@type', expandType);
2449
+ var inferred = el.value ? inferTypeFromDefault$1(unwrap(el.value)) : undefined;
2450
+ var _type = (_declared$type3 = declared === null || declared === void 0 ? void 0 : declared.type) !== null && _declared$type3 !== void 0 ? _declared$type3 : inferred;
2451
+ if (_type === undefined) {
1381
2452
  continue;
1382
2453
  }
1383
- var _extractCurlyContent2 = extractCurlyContent(line),
1384
- content = _extractCurlyContent2.content,
1385
- _nextIndex = _extractCurlyContent2.nextIndex;
1386
- var rest = line.substring(_nextIndex);
1387
- var propType = expandType(content);
1388
- var _extractNameAndOption = extractNameAndOptionality(rest),
1389
- _extractNameAndOption2 = _slicedToArray(_extractNameAndOption, 2),
1390
- _name = _extractNameAndOption2[0],
1391
- optional = _extractNameAndOption2[1];
1392
- // console.log({name, optional, propType});
1393
- var finalType = annotateOptional(propType, optional);
1394
- if (((_lastTypedef = lastTypedef) === null || _lastTypedef === void 0 ? void 0 : _lastTypedef.type) === 'object') {
1395
- lastTypedef.properties[_name] = finalType;
1396
- } else {
1397
- warn("not an extensible type", lastTypedef);
2454
+ record(ctx.fields, _name3, {
2455
+ type: _type,
2456
+ rank: declared ? FIELD_JSDOC : FIELD_INFER,
2457
+ jsdoc: declared !== undefined,
2458
+ raw: declared === null || declared === void 0 ? void 0 : declared.raw,
2459
+ readonly: el.readonly === true || (comment ? /@readonly(?![\w])/.test(comment) : false),
2460
+ optional: el.optional === true
2461
+ }, warn, className);
2462
+ } else if (el.type === 'ClassMethod' || el.type === 'ClassPrivateMethod') {
2463
+ if (el.static) {
2464
+ continue;
2465
+ }
2466
+ if (el.kind === 'constructor') {
2467
+ ctor = el;
2468
+ } else if (el.kind === 'method') {
2469
+ var _ctx$fields$_name;
2470
+ ctx.fields[_name3] = (_ctx$fields$_name = ctx.fields[_name3]) !== null && _ctx$fields$_name !== void 0 ? _ctx$fields$_name : {
2471
+ type: 'Function',
2472
+ rank: FIELD_INFER,
2473
+ jsdoc: false
2474
+ };
2475
+ } else if (el.kind === 'get') {
2476
+ ctx.gets[_name3] = el;
2477
+ } else if (el.kind === 'set') {
2478
+ ctx.sets[_name3] = el;
1398
2479
  }
1399
- } else if (line.startsWith('@callback')) {
1400
- var _name2 = line.substring(9).trim();
1401
- typedefs[_name2] = 'Function';
1402
2480
  }
1403
2481
  }
1404
2482
  } catch (err) {
1405
- _iterator.e(err);
2483
+ _iterator7.e(err);
1406
2484
  } finally {
1407
- _iterator.f();
2485
+ _iterator7.f();
2486
+ }
2487
+ for (var _i = 0, _Object$keys = Object.keys(ctx.gets); _i < _Object$keys.length; _i++) {
2488
+ var _tagType, _ref2, _getType$type, _getType$raw;
2489
+ var name = _Object$keys[_i];
2490
+ var get = ctx.gets[name];
2491
+ var set = ctx.sets[name];
2492
+ var getComment = lastBlockComment(get);
2493
+ var getType = (_tagType = tagType(getComment, '@type', expandType)) !== null && _tagType !== void 0 ? _tagType : tagType(getComment, '@returns', expandType);
2494
+ var setInfo = set ? setterType(set, expandType) : undefined;
2495
+ var type = (_ref2 = (_getType$type = getType === null || getType === void 0 ? void 0 : getType.type) !== null && _getType$type !== void 0 ? _getType$type : setInfo === null || setInfo === void 0 ? void 0 : setInfo.type) !== null && _ref2 !== void 0 ? _ref2 : 'any';
2496
+ record(ctx.fields, name, {
2497
+ type: type,
2498
+ rank: getType || setInfo !== null && setInfo !== void 0 && setInfo.jsdoc ? FIELD_JSDOC : FIELD_INFER,
2499
+ jsdoc: Boolean(getType || (setInfo === null || setInfo === void 0 ? void 0 : setInfo.jsdoc)),
2500
+ raw: (_getType$raw = getType === null || getType === void 0 ? void 0 : getType.raw) !== null && _getType$raw !== void 0 ? _getType$raw : setInfo === null || setInfo === void 0 ? void 0 : setInfo.raw,
2501
+ readonly: !set
2502
+ }, warn, className);
2503
+ }
2504
+ for (var _i2 = 0, _Object$keys2 = Object.keys(ctx.sets); _i2 < _Object$keys2.length; _i2++) {
2505
+ var _name = _Object$keys2[_i2];
2506
+ if (ctx.gets[_name]) {
2507
+ continue;
2508
+ }
2509
+ var _setInfo = setterType(ctx.sets[_name], expandType);
2510
+ record(ctx.fields, _name, {
2511
+ type: _setInfo.type,
2512
+ rank: _setInfo.jsdoc ? FIELD_JSDOC : FIELD_INFER,
2513
+ jsdoc: _setInfo.jsdoc,
2514
+ raw: _setInfo.raw
2515
+ }, warn, className);
2516
+ }
2517
+ if (ctor && ctor.body) {
2518
+ var _iterator8 = _createForOfIteratorHelper(ctor.body.body),
2519
+ _step8;
2520
+ try {
2521
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
2522
+ var st = _step8.value;
2523
+ walkStatement(st, false, ctx);
2524
+ }
2525
+ } catch (err) {
2526
+ _iterator8.e(err);
2527
+ } finally {
2528
+ _iterator8.f();
2529
+ }
1408
2530
  }
2531
+ var properties = {};
2532
+ for (var _i3 = 0, _Object$keys3 = Object.keys(ctx.fields); _i3 < _Object$keys3.length; _i3++) {
2533
+ var _name2 = _Object$keys3[_i3];
2534
+ properties[_name2] = entryToType(ctx.fields[_name2]);
2535
+ }
2536
+ if (!Object.keys(properties).length) {
2537
+ return;
2538
+ }
2539
+ return {
2540
+ name: className,
2541
+ shape: {
2542
+ type: 'object',
2543
+ properties: properties
2544
+ }
2545
+ };
1409
2546
  }
1410
2547
 
1411
2548
  /**
@@ -1454,101 +2591,6 @@
1454
2591
  return false;
1455
2592
  }
1456
2593
 
1457
- /**
1458
- * @typedef {ReturnType<typeof parseJSDoc>} ParseJSDocReturnType
1459
- */
1460
- /**
1461
- * @typedef {typeof expandTypeDepFree} ExpandType
1462
- * @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
1463
- */
1464
- /**
1465
- * Parses JSDoc comments to extract parameter type information.
1466
- *
1467
- * @param {string} src - The JSDoc comment string to parse.
1468
- * @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
1469
- * @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
1470
- */
1471
- function parseJSDoc(src) {
1472
- var expandType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : expandTypeDepFree;
1473
- // Parse something like: @param {Object} [kwargs={}] Optional arguments.
1474
- var regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\-\{\}\.'" ]+)/g;
1475
- var matches = _toConsumableArray(src.matchAll(regex));
1476
- /** @type {Record<string, ExpandTypeReturnType>} */
1477
- var params = Object.create(null);
1478
- matches.forEach(function (_) {
1479
- var type = expandType(_[1].trim());
1480
- var name = _[2].trim();
1481
- var optional = false;
1482
- // Examples:
1483
- // name: [kwargs={}] The configuration parameters.
1484
- // name: [d = 1.0] Sample spacing
1485
- if (name[0] === '[') {
1486
- // Counting opening/closing brackets for perfect match
1487
- var openCloseCount = 1;
1488
- var i = 1;
1489
- for (; i < name.length; i++) {
1490
- var c = name[i];
1491
- if (c === '[') {
1492
- openCloseCount++;
1493
- } else if (c === ']') {
1494
- openCloseCount--;
1495
- }
1496
- if (openCloseCount === 0) {
1497
- break;
1498
- }
1499
- }
1500
- // Afterwards name will be: d = 1.0
1501
- name = name.substring(1, i);
1502
- // mark it for the type:
1503
- optional = true;
1504
- }
1505
- // Strip the rest (either leftover of optional value or description)
1506
- name = name.split(' ')[0].split('=')[0].trim();
1507
- var annotatedType = annotateOptional(type, optional);
1508
- // Turn "options.stats[].unitsName" into ['options', 'stats', 'unitsName'].
1509
- var parts = name.split(/[\[\]]*\./);
1510
- var properties = params;
1511
- var _iterator = _createForOfIteratorHelper(parts),
1512
- _step;
1513
- try {
1514
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
1515
- var part = _step.value;
1516
- var toptype = properties[part];
1517
- if (!toptype) {
1518
- // No toptype means we resolved as far as possible, now we can add `annotatedType`.
1519
- console.assert(part === parts.at(-1), 'Current part and last part should be the same.');
1520
- properties[part] = annotatedType;
1521
- } else if (toptype.type === "union") {
1522
- var typeObject = toptype.members.find(function (_) {
1523
- return (_ === null || _ === void 0 ? void 0 : _.type) === 'object';
1524
- });
1525
- properties = typeObject.properties;
1526
- } else if (toptype.type === "array") {
1527
- properties = toptype.elementType.properties;
1528
- } else if (toptype.type === "object") {
1529
- toptype.properties = toptype.properties || Object.create(null);
1530
- properties = toptype.properties;
1531
- } else {
1532
- console.warn("parseJSDoc> Skipping @param, unseen syntax detected. Please check if your JSDoc is valid or open an issue about this!", {
1533
- src: src,
1534
- toptype: toptype,
1535
- parts: parts,
1536
- annotatedType: annotatedType
1537
- });
1538
- }
1539
- }
1540
- } catch (err) {
1541
- _iterator.e(err);
1542
- } finally {
1543
- _iterator.f();
1544
- }
1545
- });
1546
- if (Object.keys(params).length === 0) {
1547
- return;
1548
- }
1549
- return params;
1550
- }
1551
-
1552
2594
  /**
1553
2595
  * @param {string} src - JSDoc comment of the setter.
1554
2596
  * @param {Function} expandType - The expandType function.
@@ -1583,9 +2625,6 @@
1583
2625
  var expandType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : expandTypeDepFree;
1584
2626
  var regexTemplateTyped = /@template \{(.*?)\} ([a-zA-Z0-9_$=]+)/g;
1585
2627
  var matches = _toConsumableArray(src.matchAll(regexTemplateTyped));
1586
- if (!matches.length) {
1587
- return;
1588
- }
1589
2628
  /** @type {Record<string, ExpandTypeReturnType>} */
1590
2629
  var templates = Object.create(null);
1591
2630
  matches.forEach(function (_) {
@@ -1593,6 +2632,40 @@
1593
2632
  var name = _[2].trim();
1594
2633
  templates[name] = type;
1595
2634
  });
2635
+ // `@template [A=X]` defaults: constraint X under name A.
2636
+ var regexTemplateDefault = /@template \[([a-zA-Z0-9_$]+)=([^\]]+)\]/g;
2637
+ var _iterator = _createForOfIteratorHelper(src.matchAll(regexTemplateDefault)),
2638
+ _step;
2639
+ try {
2640
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2641
+ var match = _step.value;
2642
+ templates[match[1]] = expandType(match[2].trim());
2643
+ }
2644
+ // Bare `@template T`: unconstrained, stands in as any.
2645
+ } catch (err) {
2646
+ _iterator.e(err);
2647
+ } finally {
2648
+ _iterator.f();
2649
+ }
2650
+ var regexTemplateBare = /@template ([a-zA-Z0-9_$]+)(?![a-zA-Z0-9_$])/g;
2651
+ var _iterator2 = _createForOfIteratorHelper(src.matchAll(regexTemplateBare)),
2652
+ _step2;
2653
+ try {
2654
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2655
+ var _match = _step2.value;
2656
+ var name = _match[1];
2657
+ if (!(name in templates)) {
2658
+ templates[name] = 'any';
2659
+ }
2660
+ }
2661
+ } catch (err) {
2662
+ _iterator2.e(err);
2663
+ } finally {
2664
+ _iterator2.f();
2665
+ }
2666
+ if (!Object.keys(templates).length) {
2667
+ return;
2668
+ }
1596
2669
  return templates;
1597
2670
  }
1598
2671
 
@@ -3934,6 +5007,8 @@
3934
5007
  });
3935
5008
  /** @type {Record<string, object>} */
3936
5009
  _defineProperty(_assertThisInitialized(_this), "typedefs", {});
5010
+ /** @type {Record<string, string[]>} */
5011
+ _defineProperty(_assertThisInitialized(_this), "typedefTemplates", {});
3937
5012
  /** @type {string[]} */
3938
5013
  _defineProperty(_assertThisInitialized(_this), "addLaterImportNamespaceSpecifier", []);
3939
5014
  _this.forceCurly = forceCurly;
@@ -3996,6 +5071,21 @@
3996
5071
  var id_ = this.toSource(id);
3997
5072
  var out = _get(_getPrototypeOf(Asserter.prototype), "ClassDeclaration", this).call(this, node);
3998
5073
  out += "".concat(this.spaces, "registerClass(").concat(id_, ");");
5074
+ var harvested = harvestClassShape(node, {
5075
+ expandType: this.expandType,
5076
+ warn: this.warn.bind(this)
5077
+ });
5078
+ if (harvested) {
5079
+ // Hand-written typedefs win: emitting a second registerTypedef for the
5080
+ // same name would be last-wins deterministic but noisy, so the harvest
5081
+ // step skips names already present in this.typedefs (populated in File).
5082
+ if (this.typedefs[harvested.name]) {
5083
+ this.warn("harvestClassShape: skipping harvested shape for '".concat(harvested.name, "', hand-written typedef wins"));
5084
+ } else {
5085
+ var json = simplifyTypeToSource(harvested.shape);
5086
+ out += "\n".concat(this.spaces, "registerTypedef('").concat(harvested.name, "', ").concat(json, ");");
5087
+ }
5088
+ }
3999
5089
  return out;
4000
5090
  }
4001
5091
  /**
@@ -4910,7 +6000,7 @@
4910
6000
  for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
4911
6001
  var comment = _step5.value;
4912
6002
  var warn = this.warn.bind(this);
4913
- parseJSDocTypedef(this.typedefs, warn, comment, this.expandType);
6003
+ parseJSDocTypedef(this.typedefs, this.typedefTemplates, warn, comment, this.expandType);
4914
6004
  }
4915
6005
  } catch (err) {
4916
6006
  _iterator5.e(err);
@@ -4923,7 +6013,8 @@
4923
6013
  for (var name in this.typedefs) {
4924
6014
  var typedef = this.typedefs[name];
4925
6015
  var json = simplifyTypeToSource(typedef);
4926
- out += "registerTypedef('".concat(name, "', ").concat(json, ");\n");
6016
+ var params = this.typedefTemplates[name];
6017
+ out += params !== null && params !== void 0 && params.length ? "registerTypedef('".concat(name, "', ").concat(json, ", ").concat(JSON.stringify(params), ");\n") : "registerTypedef('".concat(name, "', ").concat(json, ");\n");
4927
6018
  }
4928
6019
  var code = this.toSource(program) + '\n';
4929
6020
  out += code;
@@ -5214,7 +6305,13 @@
5214
6305
  {
5215
6306
  var name = toSourceBabelTS(node.typeName);
5216
6307
  if (!node.typeParameters) {
5217
- // console.log(`node.typeName.name=${node.typeName.name} name=${name}`, node);
6308
+ // Bare `Object` is the empty object type, matching expandType().
6309
+ if (name === 'Object') {
6310
+ return {
6311
+ type: 'object',
6312
+ properties: {}
6313
+ };
6314
+ }
5218
6315
  // Bare reference: Identifier gives name, TSQualifiedName gives dotted path.
5219
6316
  return name;
5220
6317
  }
@@ -5275,6 +6372,8 @@
5275
6372
  }
5276
6373
  case 'TSStringKeyword':
5277
6374
  return 'string';
6375
+ case 'TSSymbolKeyword':
6376
+ return 'symbol';
5278
6377
  case 'TSNumberKeyword':
5279
6378
  return 'number';
5280
6379
  case 'TSIntersectionType':
@@ -5356,6 +6455,8 @@
5356
6455
  case 'ParenthesizedType':
5357
6456
  // fall-through for parentheses
5358
6457
  return toSourceBabelTS(node.type);
6458
+ case 'TSParenthesizedType':
6459
+ return toSourceBabelTS(node.typeAnnotation);
5359
6460
  case 'LastTypeNode':
5360
6461
  return toSourceBabelTS(node.qualifier);
5361
6462
  case 'TSTypeQuery':
@@ -5364,11 +6465,129 @@
5364
6465
  type: 'typeof',
5365
6466
  argument: argument
5366
6467
  };
6468
+ case 'TSConditionalType':
6469
+ {
6470
+ var checkType = toSourceBabelTS(node.checkType);
6471
+ var extendsType = toSourceBabelTS(node.extendsType);
6472
+ var trueType = toSourceBabelTS(node.trueType);
6473
+ var falseType = toSourceBabelTS(node.falseType);
6474
+ return {
6475
+ type: 'condition',
6476
+ checkType: checkType,
6477
+ extendsType: extendsType,
6478
+ trueType: trueType,
6479
+ falseType: falseType
6480
+ };
6481
+ }
6482
+ case 'TSIndexedAccessType':
6483
+ {
6484
+ var index = toSourceBabelTS(node.indexType);
6485
+ var object = toSourceBabelTS(node.objectType);
6486
+ return {
6487
+ type: 'indexedAccess',
6488
+ index: index,
6489
+ object: object
6490
+ };
6491
+ }
6492
+ case 'TSMappedType':
6493
+ {
6494
+ var param = node.typeParameter;
6495
+ var nameNode = param === null || param === void 0 ? void 0 : param.name;
6496
+ var element = typeof nameNode === 'string' ? nameNode : toSourceBabelTS(nameNode);
6497
+ var iterable = toSourceBabelTS(param === null || param === void 0 ? void 0 : param.constraint);
6498
+ var result = toSourceBabelTS(node.typeAnnotation);
6499
+ var out = {
6500
+ type: 'mapping',
6501
+ iterable: iterable,
6502
+ element: element,
6503
+ result: result
6504
+ };
6505
+ if (node.nameType) {
6506
+ out.nameType = toSourceBabelTS(node.nameType);
6507
+ }
6508
+ if (node.optional !== undefined && node.optional !== false && node.optional !== null) {
6509
+ out.question = node.optional === '-' ? '-' : node.optional === '+' ? '+' : '?';
6510
+ }
6511
+ if (node.readonly !== undefined && node.readonly !== false && node.readonly !== null) {
6512
+ out.readonly = node.readonly === '-' ? '-' : node.readonly === '+' ? '+' : 'readonly';
6513
+ }
6514
+ return out;
6515
+ }
6516
+ case 'TSTypeAnnotation':
6517
+ return toSourceBabelTS(node.typeAnnotation);
6518
+ case 'TSTypeLiteral':
6519
+ {
6520
+ var _node$members;
6521
+ var _properties = {};
6522
+ var indexSignatures;
6523
+ var _iterator = _createForOfIteratorHelper((_node$members = node.members) !== null && _node$members !== void 0 ? _node$members : []),
6524
+ _step;
6525
+ try {
6526
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
6527
+ var member = _step.value;
6528
+ if (member.type === 'TSIndexSignature') {
6529
+ var _indexSignatures;
6530
+ indexSignatures = (_indexSignatures = indexSignatures) !== null && _indexSignatures !== void 0 ? _indexSignatures : [];
6531
+ indexSignatures.push(toSourceBabelTS(member));
6532
+ } else if (member.type === 'TSPropertySignature') {
6533
+ var _name = toSourceBabelTS(member.key);
6534
+ var type = toSourceBabelTS(member.typeAnnotation);
6535
+ if (member.optional) {
6536
+ if (type && _typeof(type) === 'object') type.optional = true;else type = {
6537
+ type: type,
6538
+ optional: true
6539
+ };
6540
+ }
6541
+ if (member.readonly) {
6542
+ if (type && _typeof(type) === 'object') type.readonly = true;else type = {
6543
+ type: type,
6544
+ readonly: true
6545
+ };
6546
+ }
6547
+ _properties[_name] = type;
6548
+ } else {
6549
+ console.warn('TSTypeLiteral: unhandled member', member.type);
6550
+ }
6551
+ }
6552
+ } catch (err) {
6553
+ _iterator.e(err);
6554
+ } finally {
6555
+ _iterator.f();
6556
+ }
6557
+ var ret = {
6558
+ type: 'object'
6559
+ };
6560
+ if (Object.keys(_properties).length) {
6561
+ ret.properties = _properties;
6562
+ }
6563
+ if (indexSignatures) {
6564
+ ret.indexSignatures = indexSignatures;
6565
+ }
6566
+ return ret;
6567
+ }
6568
+ case 'TSIndexSignature':
6569
+ {
6570
+ var _node$parameters;
6571
+ var indexType = toSourceBabelTS(node.typeAnnotation);
6572
+ var indexParameters = ((_node$parameters = node.parameters) !== null && _node$parameters !== void 0 ? _node$parameters : []).map(toSourceBabelTS);
6573
+ return {
6574
+ type: 'indexSignature',
6575
+ indexType: indexType,
6576
+ indexParameters: indexParameters
6577
+ };
6578
+ }
5367
6579
  case 'TSTypeOperator':
5368
6580
  if (node.operator === 'readonly') {
5369
6581
  // readonly erased at runtime, same shape as the inner type.
5370
6582
  return toSourceBabelTS(node.typeAnnotation);
5371
6583
  }
6584
+ if (node.operator === 'keyof') {
6585
+ var keyofArg = toSourceBabelTS(node.typeAnnotation);
6586
+ return {
6587
+ type: 'keyof',
6588
+ argument: keyofArg
6589
+ };
6590
+ }
5372
6591
  console.warn('unimplemented TSTypeOperator', node.operator);
5373
6592
  return 'any';
5374
6593
  case 'TSQualifiedName':
@@ -5501,6 +6720,8 @@
5501
6720
  _defineProperty(this, "parents", []);
5502
6721
  /** @type {Record<string, object>} */
5503
6722
  _defineProperty(this, "typedefs", {});
6723
+ /** @type {Record<string, string[]>} */
6724
+ _defineProperty(this, "typedefTemplates", {});
5504
6725
  /** @type {import('./parseJSDoc.js').ExpandType} */
5505
6726
  this.expandType = options.expandType || expandTypeDepFree;
5506
6727
  }
@@ -5521,7 +6742,7 @@
5521
6742
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
5522
6743
  var comment = _step.value;
5523
6744
  var warn = console.warn.bind(console);
5524
- parseJSDocTypedef(this.typedefs, warn, comment, this.expandType);
6745
+ parseJSDocTypedef(this.typedefs, this.typedefTemplates, warn, comment, this.expandType);
5525
6746
  }
5526
6747
  } catch (err) {
5527
6748
  _iterator.e(err);