@runtime-type-inspector/transpiler 5.0.1 → 5.0.3

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 +1522 -296
  2. package/index.mjs +1419 -288
  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,109 @@
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
+ // Bare `{}` carries no `properties` key, matching the TS/Babel parsers
1522
+ // (and distinguishing it from the `object` keyword, which does).
1523
+ } catch (err) {
1524
+ _iterator.e(err);
1525
+ } finally {
1526
+ _iterator.f();
1527
+ }
1528
+ if (!Object.keys(properties).length) {
1529
+ return {
1530
+ type: 'object'
1531
+ };
1532
+ }
1144
1533
  return {
1145
1534
  type: 'object',
1146
1535
  properties: properties
1147
1536
  };
1148
1537
  }
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
- });
1538
+ // (5) Unions and intersections via top-level splits (`&` binds tighter, so `|` first).
1539
+ // Note: conditionals already handled up front (before nullable) so `D?`
1540
+ // false branches keep their nullability; this slot is intentionally union-only.
1541
+ var unionParts = splitTopLevel(type, "|");
1542
+ if (unionParts.length >= 2) {
1155
1543
  return {
1156
1544
  type: 'union',
1157
- members: members.map(expandTypeDepFree)
1545
+ members: unionParts.map(function (_) {
1546
+ return expandTypeDepFree(_.trim());
1547
+ })
1548
+ };
1549
+ }
1550
+ var interParts = splitTopLevel(type, "&");
1551
+ if (interParts.length >= 2) {
1552
+ return {
1553
+ type: 'intersection',
1554
+ members: interParts.map(function (_) {
1555
+ return expandTypeDepFree(_.trim());
1556
+ })
1557
+ };
1558
+ }
1559
+ // (7) `keyof T` before typeof so `keyof typeof X` nests correctly.
1560
+ if (type.startsWith('keyof ') || type.startsWith('keyof(')) {
1561
+ var after = type.startsWith('keyof(') ? type.slice(5).trim() : type.slice(6).trim();
1562
+ if (after) {
1563
+ return {
1564
+ type: 'keyof',
1565
+ argument: expandTypeDepFree(after)
1566
+ };
1567
+ }
1568
+ }
1569
+ // (8) Indexed access `T[K]` (arrays `T[]` handled below, tuples above start with `[`).
1570
+ var indexed = parseIndexedAccessRaw(type);
1571
+ if (indexed) {
1572
+ return {
1573
+ type: 'indexedAccess',
1574
+ index: expandTypeDepFree(indexed.indexRaw),
1575
+ object: expandTypeDepFree(indexed.objectRaw)
1158
1576
  };
1159
1577
  }
1160
- // (6) expand [] Arrays
1578
+ // (9) expand [] Arrays
1161
1579
  // Test arrays: new pc.Mat3().set([1, 2, 3, "asd"])
1162
1580
  if (type.endsWith("[]")) {
1163
1581
  var _typeSlice2 = type.slice(0, -2);
@@ -1166,17 +1584,26 @@
1166
1584
  elementType: expandTypeDepFree(_typeSlice2)
1167
1585
  };
1168
1586
  }
1169
- // (7) expand tuples
1587
+ // (10) expand tuples
1170
1588
  if (type[0] === '[' && type[type.length - 1] === ']') {
1171
- var elements = type.slice(1, -1).split(','); // ['null', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture']
1589
+ var _inner2 = type.slice(1, -1);
1590
+ if (_inner2.trim() === '') {
1591
+ return {
1592
+ type: 'tuple',
1593
+ elements: []
1594
+ };
1595
+ }
1596
+ var elements = splitTopLevel(_inner2, ','); // ['null', ' Texture', ...]
1172
1597
  return {
1173
1598
  type: 'tuple',
1174
- elements: elements.map(expandTypeDepFree)
1599
+ elements: elements.map(function (_) {
1600
+ return expandTypeDepFree(_.trim());
1601
+ })
1175
1602
  };
1176
1603
  }
1177
- // (8) expand typeof expressions
1604
+ // (11) expand typeof expressions
1178
1605
  if (type.startsWith('typeof ')) {
1179
- var argument = expandTypeDepFree(type.substring(7));
1606
+ var argument = expandTypeDepFree(type.substring(7).trim());
1180
1607
  return {
1181
1608
  type: 'typeof',
1182
1609
  argument: argument
@@ -1202,79 +1629,6 @@
1202
1629
  return type;
1203
1630
  }
1204
1631
 
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
1632
  /**
1279
1633
  * Extracts the parameter name and its optionality from a JSDoc parameter string.
1280
1634
  *
@@ -1339,73 +1693,863 @@
1339
1693
  *
1340
1694
  * It iterates through the lines of a `CommentBlock` from the Babel AST, looking for `@typedef` and `@property`
1341
1695
  * 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.
1696
+ * it adds it to the last found typedef if it is an object type. `@template` names preceding a
1697
+ * typedef are recorded in `typedefTemplates` so generic references can instantiate.
1343
1698
  * @param {Record<string, object>} typedefs - An object to store typedefs, mapping type names to their expanded definitions.
1699
+ * @param {Record<string, string[]>} typedefTemplates - An object to store template parameter names per generic typedef.
1344
1700
  * @param {Console["warn"]} warn - A warn function used for emitting warnings about non-extensible types.
1345
1701
  * @param {import("@babel/types").Comment} comment - A comment extracted from Babel's AST, expected to be a CommentBlock containing type definitions.
1346
1702
  * @param {Function} expandType - A function that takes a type expression as a string and returns a structured representation of the type.
1347
1703
  */
1348
- function parseJSDocTypedef(typedefs, warn, comment, expandType) {
1349
- var type = comment.type,
1350
- value = comment.value;
1351
- if (type !== 'CommentBlock') {
1704
+ function parseJSDocTypedef(typedefs, typedefTemplates, warn, comment, expandType) {
1705
+ var type = comment.type,
1706
+ value = comment.value;
1707
+ if (type !== 'CommentBlock') {
1708
+ return;
1709
+ }
1710
+ var lines = value.split('\n');
1711
+ var lastTypedef;
1712
+ var pendingTemplates = [];
1713
+ /**
1714
+ * @param {string} line - The trimmed JSDoc line.
1715
+ * @returns {boolean} True when the line held a template tag.
1716
+ */
1717
+ function harvestTemplate(line) {
1718
+ var match = line.match(/@template \{(.*?)\} ([a-zA-Z0-9_$]+)/);
1719
+ if (match) {
1720
+ pendingTemplates.push(match[2]);
1721
+ return true;
1722
+ }
1723
+ match = line.match(/@template (?:\{.*?\} )?\[([a-zA-Z0-9_$]+)=/);
1724
+ if (match) {
1725
+ pendingTemplates.push(match[1]);
1726
+ return true;
1727
+ }
1728
+ match = line.match(/@template ([a-zA-Z0-9_$]+)(?![a-zA-Z0-9_$])/);
1729
+ if (match) {
1730
+ pendingTemplates.push(match[1]);
1731
+ return true;
1732
+ }
1733
+ return false;
1734
+ }
1735
+ var _iterator = _createForOfIteratorHelper(lines),
1736
+ _step;
1737
+ try {
1738
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1739
+ var line = _step.value;
1740
+ line = line.trim();
1741
+ if (line[0] === '*') {
1742
+ line = line.slice(1).trim();
1743
+ }
1744
+ if (line.startsWith('@template')) {
1745
+ harvestTemplate(line);
1746
+ } else if (line.startsWith('@typedef')) {
1747
+ var _extractCurlyContent = extractCurlyContent(line),
1748
+ def = _extractCurlyContent.content,
1749
+ nextIndex = _extractCurlyContent.nextIndex;
1750
+ var name = line.substring(nextIndex).trim();
1751
+ // Drop description
1752
+ name = name.split(' ')[0];
1753
+ lastTypedef = expandType(def);
1754
+ // Ignore @typedef's that only refer to themselves in another file (see typedef-overwrite test)
1755
+ if (lastTypedef !== name) {
1756
+ typedefs[name] = lastTypedef;
1757
+ if (pendingTemplates.length) {
1758
+ typedefTemplates[name] = _toConsumableArray(pendingTemplates);
1759
+ }
1760
+ }
1761
+ pendingTemplates = [];
1762
+ } else if (line.startsWith('@property')) {
1763
+ var _lastTypedef;
1764
+ // class @property
1765
+ if (!lastTypedef) {
1766
+ continue;
1767
+ }
1768
+ var _extractCurlyContent2 = extractCurlyContent(line),
1769
+ content = _extractCurlyContent2.content,
1770
+ _nextIndex = _extractCurlyContent2.nextIndex;
1771
+ var rest = line.substring(_nextIndex);
1772
+ var propType = expandType(content);
1773
+ var _extractNameAndOption = extractNameAndOptionality(rest),
1774
+ _extractNameAndOption2 = _slicedToArray(_extractNameAndOption, 2),
1775
+ _name = _extractNameAndOption2[0],
1776
+ optional = _extractNameAndOption2[1];
1777
+ // console.log({name, optional, propType});
1778
+ var finalType = annotateOptional(propType, optional);
1779
+ if (((_lastTypedef = lastTypedef) === null || _lastTypedef === void 0 ? void 0 : _lastTypedef.type) === 'object') {
1780
+ lastTypedef.properties[_name] = finalType;
1781
+ } else {
1782
+ warn("not an extensible type", lastTypedef);
1783
+ }
1784
+ } else if (line.startsWith('@callback')) {
1785
+ var _name2 = line.substring(9).trim();
1786
+ typedefs[_name2] = 'Function';
1787
+ }
1788
+ }
1789
+ } catch (err) {
1790
+ _iterator.e(err);
1791
+ } finally {
1792
+ _iterator.f();
1793
+ }
1794
+ }
1795
+
1796
+ /**
1797
+ * @typedef {ReturnType<typeof parseJSDoc>} ParseJSDocReturnType
1798
+ */
1799
+ /**
1800
+ * @typedef {typeof expandTypeDepFree} ExpandType
1801
+ * @typedef {ReturnType<ExpandType>} ExpandTypeReturnType
1802
+ */
1803
+ /**
1804
+ * Parses JSDoc comments to extract parameter type information.
1805
+ *
1806
+ * @param {string} src - The JSDoc comment string to parse.
1807
+ * @param {ExpandType} [expandType] - An optional function to process the types found in the JSDoc.
1808
+ * @returns {Record<string, ExpandTypeReturnType> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
1809
+ */
1810
+ function parseJSDoc(src) {
1811
+ var expandType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : expandTypeDepFree;
1812
+ // Parse something like: @param {Object} [kwargs={}] Optional arguments.
1813
+ var regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\-\{\}\.'" ]+)/g;
1814
+ var matches = _toConsumableArray(src.matchAll(regex));
1815
+ /** @type {Record<string, ExpandTypeReturnType>} */
1816
+ var params = Object.create(null);
1817
+ matches.forEach(function (_) {
1818
+ var type = expandType(_[1].trim());
1819
+ var name = _[2].trim();
1820
+ var optional = false;
1821
+ // Examples:
1822
+ // name: [kwargs={}] The configuration parameters.
1823
+ // name: [d = 1.0] Sample spacing
1824
+ if (name[0] === '[') {
1825
+ // Counting opening/closing brackets for perfect match
1826
+ var openCloseCount = 1;
1827
+ var i = 1;
1828
+ for (; i < name.length; i++) {
1829
+ var c = name[i];
1830
+ if (c === '[') {
1831
+ openCloseCount++;
1832
+ } else if (c === ']') {
1833
+ openCloseCount--;
1834
+ }
1835
+ if (openCloseCount === 0) {
1836
+ break;
1837
+ }
1838
+ }
1839
+ // Afterwards name will be: d = 1.0
1840
+ name = name.substring(1, i);
1841
+ // mark it for the type:
1842
+ optional = true;
1843
+ }
1844
+ // Strip the rest (either leftover of optional value or description)
1845
+ name = name.split(' ')[0].split('=')[0].trim();
1846
+ var annotatedType = annotateOptional(type, optional);
1847
+ // Turn "options.stats[].unitsName" into ['options', 'stats', 'unitsName'].
1848
+ var parts = name.split(/[\[\]]*\./);
1849
+ var properties = params;
1850
+ var _iterator = _createForOfIteratorHelper(parts),
1851
+ _step;
1852
+ try {
1853
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1854
+ var part = _step.value;
1855
+ var toptype = properties[part];
1856
+ if (!toptype) {
1857
+ // No toptype means we resolved as far as possible, now we can add `annotatedType`.
1858
+ console.assert(part === parts.at(-1), 'Current part and last part should be the same.');
1859
+ properties[part] = annotatedType;
1860
+ } else if (toptype.type === "union") {
1861
+ var typeObject = toptype.members.find(function (_) {
1862
+ return (_ === null || _ === void 0 ? void 0 : _.type) === 'object';
1863
+ });
1864
+ properties = typeObject.properties;
1865
+ } else if (toptype.type === "array") {
1866
+ properties = toptype.elementType.properties;
1867
+ } else if (toptype.type === "object") {
1868
+ toptype.properties = toptype.properties || Object.create(null);
1869
+ properties = toptype.properties;
1870
+ } else {
1871
+ console.warn("parseJSDoc> Skipping @param, unseen syntax detected. Please check if your JSDoc is valid or open an issue about this!", {
1872
+ src: src,
1873
+ toptype: toptype,
1874
+ parts: parts,
1875
+ annotatedType: annotatedType
1876
+ });
1877
+ }
1878
+ }
1879
+ } catch (err) {
1880
+ _iterator.e(err);
1881
+ } finally {
1882
+ _iterator.f();
1883
+ }
1884
+ });
1885
+ if (Object.keys(params).length === 0) {
1886
+ return;
1887
+ }
1888
+ return params;
1889
+ }
1890
+
1891
+ /**
1892
+ * Infers a parameter type from its default value AST node.
1893
+ * Returns widened types like TypeScript does (`= 0` means `number`, not
1894
+ * literal `0`; `= null` widens to `any`).
1895
+ * Returns `undefined` when nothing useful can be inferred — the caller then
1896
+ * emits no check, exactly like an undocumented parameter today.
1897
+ * Shapes match what `expandType` produces so they can be embedded as-is.
1898
+ * @param {import('@babel/types').Node} node - The default value AST node.
1899
+ * @returns {string | object | undefined} Inferred type or `undefined` to skip.
1900
+ */
1901
+ function inferTypeFromDefault$1(node) {
1902
+ if (!node) {
1903
+ return;
1904
+ }
1905
+ switch (node.type) {
1906
+ case 'NumericLiteral':
1907
+ return 'number';
1908
+ case 'StringLiteral':
1909
+ return 'string';
1910
+ case 'BooleanLiteral':
1911
+ return 'boolean';
1912
+ case 'BigIntLiteral':
1913
+ return {
1914
+ type: 'bigint'
1915
+ };
1916
+ case 'RegExpLiteral':
1917
+ return 'RegExp';
1918
+ case 'TemplateLiteral':
1919
+ return 'string';
1920
+ case 'ArrayExpression':
1921
+ return {
1922
+ type: 'array',
1923
+ elementType: 'any'
1924
+ };
1925
+ case 'ObjectExpression':
1926
+ return {
1927
+ type: 'object',
1928
+ properties: {}
1929
+ };
1930
+ case 'ArrowFunctionExpression':
1931
+ case 'FunctionExpression':
1932
+ return 'Function';
1933
+ case 'NewExpression':
1934
+ {
1935
+ var callee = node.callee;
1936
+ if (callee.type === 'Identifier') {
1937
+ return callee.name;
1938
+ }
1939
+ break;
1940
+ }
1941
+ case 'UnaryExpression':
1942
+ {
1943
+ var operator = node.operator,
1944
+ argument = node.argument;
1945
+ if (operator === '!') {
1946
+ return 'boolean';
1947
+ }
1948
+ if (operator === 'void' || operator === 'typeof') {
1949
+ return operator === 'void' ? 'undefined' : 'string';
1950
+ }
1951
+ if ((operator === '-' || operator === '+') && argument.type === 'NumericLiteral') {
1952
+ return 'number';
1953
+ }
1954
+ if ((operator === '-' || operator === '+') && argument.type === 'BigIntLiteral') {
1955
+ return {
1956
+ type: 'bigint'
1957
+ };
1958
+ }
1959
+ break;
1960
+ }
1961
+ }
1962
+ }
1963
+
1964
+ // Declaration-site ranks: the field always beats constructor assignments,
1965
+ // like TSC where the declared type wins over assigned values.
1966
+ var FIELD_JSDOC = 3;
1967
+ var CTOR_JSDOC = 2;
1968
+ var FIELD_INFER = 1;
1969
+ var CTOR_INFER = 0;
1970
+ /**
1971
+ * Reads the last block comment attached to a node.
1972
+ * @param {*} node - Babel AST node.
1973
+ * @returns {string|undefined} Comment text or undefined.
1974
+ */
1975
+ function lastBlockComment(node) {
1976
+ var list = node === null || node === void 0 ? void 0 : node.leadingComments;
1977
+ if (!list) {
1978
+ return;
1979
+ }
1980
+ for (var i = list.length - 1; i >= 0; i--) {
1981
+ if (list[i].type === 'CommentBlock') {
1982
+ return list[i].value;
1983
+ }
1984
+ }
1985
+ }
1986
+ /**
1987
+ * Extracts a `{...}`-typed JSDoc tag (`@type`, `@returns`) from a comment.
1988
+ * @param {string|undefined} comment - Comment text.
1989
+ * @param {string} tag - Tag name including `@`.
1990
+ * @param {Function} expandType - String type to structured type.
1991
+ * @returns {{type: *, raw: string}|undefined} Expanded type plus raw text.
1992
+ */
1993
+ function tagType(comment, tag, expandType) {
1994
+ if (!comment) {
1995
+ return;
1996
+ }
1997
+ var idx = comment.search(new RegExp("".concat(tag, "(?=[\\s{])")));
1998
+ if (idx === -1) {
1999
+ return;
2000
+ }
2001
+ var slice = comment.slice(idx);
2002
+ if (!slice.includes('{')) {
2003
+ return;
2004
+ }
2005
+ try {
2006
+ var _extractCurlyContent = extractCurlyContent(slice),
2007
+ content = _extractCurlyContent.content;
2008
+ if (!content || !content.trim()) {
2009
+ return;
2010
+ }
2011
+ return {
2012
+ type: expandType(content.trim()),
2013
+ raw: content.trim()
2014
+ };
2015
+ } catch (_unused) {
2016
+ // Unparseable annotation: fail open, harvest continues.
2017
+ }
2018
+ }
2019
+ /**
2020
+ * Reads a static property name: identifiers and string literals, including
2021
+ * computed `['name']` forms. Anything dynamic yields undefined.
2022
+ * @param {*} key - Babel key node.
2023
+ * @param {boolean} computed - Whether the key position is computed.
2024
+ * @returns {string|undefined} Property name or undefined.
2025
+ */
2026
+ function keyName(key, computed) {
2027
+ if (!key) {
2028
+ return;
2029
+ }
2030
+ if (key.type === 'Identifier' && !computed) {
2031
+ return key.name;
2032
+ }
2033
+ if (key.type === 'StringLiteral') {
2034
+ return key.value;
2035
+ }
2036
+ }
2037
+ /**
2038
+ * Unwraps parenthesized and TS `as`/`satisfies` nodes.
2039
+ * @param {*} node - Babel AST node.
2040
+ * @returns {*} Unwrapped node.
2041
+ */
2042
+ function unwrap(node) {
2043
+ while (node && (node.type === 'ParenthesizedExpression' || node.type === 'TSAsExpression' || node.type === 'TSSatisfiesExpression')) {
2044
+ node = node.expression;
2045
+ }
2046
+ return node;
2047
+ }
2048
+ /**
2049
+ * Merges a harvested entry: higher rank wins the type, flags accumulate
2050
+ * (`optional` reflects runtime reality, `@readonly` anywhere counts).
2051
+ * Two disagreeing humans (JSDoc vs JSDoc) warn, like a TSC error.
2052
+ * @param {Record<string, object>} fields - Collected entries by name.
2053
+ * @param {string} name - Property name.
2054
+ * @param {object} entry - Entry with type, rank, raw, readonly, optional.
2055
+ * @param {Function} warn - Transpile-time warning function.
2056
+ * @param {string} className - Class name for messages.
2057
+ */
2058
+ function record(fields, name, entry, warn, className) {
2059
+ var existing = fields[name];
2060
+ if (!existing) {
2061
+ fields[name] = entry;
2062
+ return;
2063
+ }
2064
+ if (entry.jsdoc && existing.jsdoc && entry.raw !== existing.raw) {
2065
+ warn("harvestClassShape: ".concat(className, ".").concat(name, " has conflicting JSDoc types (").concat(existing.raw, " vs ").concat(entry.raw, ")"));
2066
+ }
2067
+ var winner = entry.rank >= existing.rank ? entry : existing;
2068
+ winner.readonly = existing.readonly || entry.readonly;
2069
+ winner.optional = existing.optional || entry.optional;
2070
+ fields[name] = winner;
2071
+ }
2072
+ /**
2073
+ * Turns a collected entry into a type struct, applying flags.
2074
+ * @param {object} entry - Collected entry.
2075
+ * @returns {*} Type struct or name.
2076
+ */
2077
+ function entryToType(entry) {
2078
+ var _entry$type;
2079
+ var t = (_entry$type = entry.type) !== null && _entry$type !== void 0 ? _entry$type : 'any';
2080
+ if (!entry.readonly && !entry.optional) {
2081
+ return t;
2082
+ }
2083
+ if (t && _typeof(t) === 'object') {
2084
+ return _objectSpread2(_objectSpread2(_objectSpread2({}, t), entry.readonly ? {
2085
+ readonly: true
2086
+ } : {}), entry.optional ? {
2087
+ optional: true
2088
+ } : {});
2089
+ }
2090
+ var out = {
2091
+ type: t
2092
+ };
2093
+ if (entry.readonly) {
2094
+ out.readonly = true;
2095
+ }
2096
+ if (entry.optional) {
2097
+ out.optional = true;
2098
+ }
2099
+ return out;
2100
+ }
2101
+ /**
2102
+ * Records `this.x = ...` (or `+=`, `++`) assignments.
2103
+ * @param {*} left - Assignment target.
2104
+ * @param {*} right - Assigned value or undefined for op-assign/update.
2105
+ * @param {string|undefined} comment - Leading comment text.
2106
+ * @param {boolean} conditional - True inside conditionals (counts as optional).
2107
+ * @param {object} ctx - Harvest context with fields, expandType, warn, className.
2108
+ */
2109
+ function recordThisAssign(left, right, comment, conditional, ctx) {
2110
+ var _declared$type;
2111
+ var fields = ctx.fields,
2112
+ expandType = ctx.expandType,
2113
+ warn = ctx.warn,
2114
+ className = ctx.className;
2115
+ left = unwrap(left);
2116
+ if (!left || left.type !== 'MemberExpression' || left.computed) {
2117
+ return;
2118
+ }
2119
+ if (!left.object || left.object.type !== 'ThisExpression') {
2120
+ return;
2121
+ }
2122
+ var name = keyName(left.property, false);
2123
+ if (name === undefined) {
2124
+ return;
2125
+ }
2126
+ var declared = tagType(comment, '@type', expandType);
2127
+ if (right === undefined) {
2128
+ // Op-assign/update (`+=`, `++`): exists, type unknown.
2129
+ record(fields, name, {
2130
+ type: 'any',
2131
+ rank: CTOR_INFER,
2132
+ jsdoc: false,
2133
+ optional: conditional
2134
+ }, warn, className);
2135
+ return;
2136
+ }
2137
+ var inferred = inferTypeFromDefault$1(unwrap(right));
2138
+ var type = (_declared$type = declared === null || declared === void 0 ? void 0 : declared.type) !== null && _declared$type !== void 0 ? _declared$type : inferred;
2139
+ if (type === undefined) {
2140
+ return;
2141
+ }
2142
+ record(fields, name, {
2143
+ type: type,
2144
+ rank: declared ? CTOR_JSDOC : CTOR_INFER,
2145
+ jsdoc: declared !== undefined,
2146
+ raw: declared === null || declared === void 0 ? void 0 : declared.raw,
2147
+ optional: conditional
2148
+ }, warn, className);
2149
+ }
2150
+ /**
2151
+ * Handles `Object.assign(this, {...})` with inline object literals.
2152
+ * @param {*} node - CallExpression node.
2153
+ * @param {boolean} conditional - True inside conditionals.
2154
+ * @param {object} ctx - Harvest context.
2155
+ * @returns {boolean} True when the call was `Object.assign` on `this`.
2156
+ */
2157
+ function recordObjectAssign(node, conditional, ctx) {
2158
+ var _callee$object, _callee$property;
2159
+ var fields = ctx.fields,
2160
+ expandType = ctx.expandType,
2161
+ warn = ctx.warn,
2162
+ className = ctx.className;
2163
+ var callee = node.callee,
2164
+ args = node.arguments;
2165
+ if (!callee || callee.type !== 'MemberExpression' || callee.computed) {
2166
+ return false;
2167
+ }
2168
+ if (((_callee$object = callee.object) === null || _callee$object === void 0 ? void 0 : _callee$object.type) !== 'Identifier' || callee.object.name !== 'Object') {
2169
+ return false;
2170
+ }
2171
+ if (((_callee$property = callee.property) === null || _callee$property === void 0 ? void 0 : _callee$property.type) !== 'Identifier' || callee.property.name !== 'assign') {
2172
+ return false;
2173
+ }
2174
+ if (!args.length || args[0].type !== 'ThisExpression') {
2175
+ return false;
2176
+ }
2177
+ var _iterator = _createForOfIteratorHelper(args.slice(1)),
2178
+ _step;
2179
+ try {
2180
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2181
+ var arg = _step.value;
2182
+ if (!arg || arg.type !== 'ObjectExpression') {
2183
+ continue;
2184
+ }
2185
+ var _iterator2 = _createForOfIteratorHelper(arg.properties),
2186
+ _step2;
2187
+ try {
2188
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2189
+ var _declared$type2;
2190
+ var prop = _step2.value;
2191
+ if (!prop || prop.type !== 'ObjectProperty') {
2192
+ continue;
2193
+ }
2194
+ var name = keyName(prop.key, prop.computed);
2195
+ if (name === undefined) {
2196
+ continue;
2197
+ }
2198
+ var comment = lastBlockComment(prop);
2199
+ var declared = tagType(comment, '@type', expandType);
2200
+ 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));
2201
+ if (type === undefined) {
2202
+ continue;
2203
+ }
2204
+ record(fields, name, {
2205
+ type: type,
2206
+ rank: declared ? CTOR_JSDOC : CTOR_INFER,
2207
+ jsdoc: declared !== undefined,
2208
+ raw: declared === null || declared === void 0 ? void 0 : declared.raw,
2209
+ optional: conditional
2210
+ }, warn, className);
2211
+ }
2212
+ } catch (err) {
2213
+ _iterator2.e(err);
2214
+ } finally {
2215
+ _iterator2.f();
2216
+ }
2217
+ }
2218
+ } catch (err) {
2219
+ _iterator.e(err);
2220
+ } finally {
2221
+ _iterator.f();
2222
+ }
2223
+ return true;
2224
+ }
2225
+ /**
2226
+ * Walks an expression for `this.x` writes. Stops at function boundaries:
2227
+ * deferred writes (callbacks) are out of scope.
2228
+ * @param {*} expr - Babel expression node.
2229
+ * @param {boolean} conditional - True inside conditionals.
2230
+ * @param {object} ctx - Harvest context.
2231
+ * @param {*} stmt - Enclosing statement carrying leading comments.
2232
+ */
2233
+ function walkExpression(expr, conditional, ctx, stmt) {
2234
+ if (!expr) {
2235
+ return;
2236
+ }
2237
+ switch (expr.type) {
2238
+ case 'AssignmentExpression':
2239
+ {
2240
+ var comment = lastBlockComment(stmt);
2241
+ if (expr.operator === '=') {
2242
+ recordThisAssign(expr.left, expr.right, comment, conditional, ctx);
2243
+ walkExpression(expr.right, conditional, ctx, stmt);
2244
+ } else {
2245
+ recordThisAssign(expr.left, undefined, comment, conditional, ctx);
2246
+ }
2247
+ break;
2248
+ }
2249
+ case 'UpdateExpression':
2250
+ recordThisAssign(expr.argument, undefined, lastBlockComment(stmt), conditional, ctx);
2251
+ break;
2252
+ case 'SequenceExpression':
2253
+ var _iterator3 = _createForOfIteratorHelper(expr.expressions),
2254
+ _step3;
2255
+ try {
2256
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2257
+ var each = _step3.value;
2258
+ walkExpression(each, conditional, ctx, stmt);
2259
+ }
2260
+ } catch (err) {
2261
+ _iterator3.e(err);
2262
+ } finally {
2263
+ _iterator3.f();
2264
+ }
2265
+ break;
2266
+ case 'LogicalExpression':
2267
+ walkExpression(expr.right, true, ctx, stmt);
2268
+ break;
2269
+ case 'ConditionalExpression':
2270
+ walkExpression(expr.consequent, true, ctx, stmt);
2271
+ walkExpression(expr.alternate, true, ctx, stmt);
2272
+ break;
2273
+ case 'CallExpression':
2274
+ recordObjectAssign(expr, conditional, ctx);
2275
+ break;
2276
+ }
2277
+ }
2278
+ /**
2279
+ * Walks a statement for `this.x` writes. Conditional wrappers mark entries
2280
+ * optional; nested functions are boundaries.
2281
+ * @param {*} st - Babel statement node.
2282
+ * @param {boolean} conditional - True inside conditionals.
2283
+ * @param {object} ctx - Harvest context.
2284
+ */
2285
+ function walkStatement(st, conditional, ctx) {
2286
+ if (!st) {
2287
+ return;
2288
+ }
2289
+ switch (st.type) {
2290
+ case 'BlockStatement':
2291
+ var _iterator4 = _createForOfIteratorHelper(st.body),
2292
+ _step4;
2293
+ try {
2294
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
2295
+ var each = _step4.value;
2296
+ walkStatement(each, conditional, ctx);
2297
+ }
2298
+ } catch (err) {
2299
+ _iterator4.e(err);
2300
+ } finally {
2301
+ _iterator4.f();
2302
+ }
2303
+ break;
2304
+ case 'ExpressionStatement':
2305
+ walkExpression(st.expression, conditional, ctx, st);
2306
+ break;
2307
+ case 'ReturnStatement':
2308
+ walkExpression(st.argument, conditional, ctx, st);
2309
+ break;
2310
+ case 'IfStatement':
2311
+ walkStatement(st.consequent, true, ctx);
2312
+ walkStatement(st.alternate, true, ctx);
2313
+ break;
2314
+ case 'WhileStatement':
2315
+ case 'ForStatement':
2316
+ case 'ForInStatement':
2317
+ case 'ForOfStatement':
2318
+ walkStatement(st.body, true, ctx);
2319
+ break;
2320
+ case 'DoWhileStatement':
2321
+ walkStatement(st.body, conditional, ctx);
2322
+ break;
2323
+ case 'SwitchStatement':
2324
+ var _iterator5 = _createForOfIteratorHelper(st.cases),
2325
+ _step5;
2326
+ try {
2327
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
2328
+ var _each = _step5.value;
2329
+ var _iterator6 = _createForOfIteratorHelper(_each.consequent),
2330
+ _step6;
2331
+ try {
2332
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
2333
+ var consequent = _step6.value;
2334
+ walkStatement(consequent, true, ctx);
2335
+ }
2336
+ } catch (err) {
2337
+ _iterator6.e(err);
2338
+ } finally {
2339
+ _iterator6.f();
2340
+ }
2341
+ }
2342
+ } catch (err) {
2343
+ _iterator5.e(err);
2344
+ } finally {
2345
+ _iterator5.f();
2346
+ }
2347
+ break;
2348
+ case 'TryStatement':
2349
+ walkStatement(st.block, conditional, ctx);
2350
+ if (st.handler) {
2351
+ walkStatement(st.handler.body, true, ctx);
2352
+ }
2353
+ if (st.finalizer) {
2354
+ walkStatement(st.finalizer, conditional, ctx);
2355
+ }
2356
+ break;
2357
+ case 'LabeledStatement':
2358
+ walkStatement(st.body, conditional, ctx);
2359
+ break;
2360
+ }
2361
+ }
2362
+ /**
2363
+ * Reads the single parameter name of a setter.
2364
+ * @param {*} param - Babel parameter node.
2365
+ * @returns {string|undefined} Name or undefined.
2366
+ */
2367
+ function setterParamName(param) {
2368
+ if (!param) {
2369
+ return;
2370
+ }
2371
+ if (param.type === 'Identifier') {
2372
+ return param.name;
2373
+ }
2374
+ if (param.type === 'AssignmentPattern' && param.left.type === 'Identifier') {
2375
+ return param.left.name;
2376
+ }
2377
+ }
2378
+ /**
2379
+ * Reads a setter's value type: its `@param` JSDoc first, `@type` fallback.
2380
+ * @param {*} set - Babel ClassMethod (kind set) node.
2381
+ * @param {Function} expandType - String type to structured type.
2382
+ * @returns {{type: *, raw: string|undefined, jsdoc: boolean}} Type plus metadata.
2383
+ */
2384
+ function setterType(set, expandType) {
2385
+ var setComment = lastBlockComment(set);
2386
+ var paramName = setterParamName(set.params[0]);
2387
+ var params = parseJSDoc(setComment !== null && setComment !== void 0 ? setComment : '', expandType);
2388
+ if (paramName && params && params[paramName] !== undefined) {
2389
+ return {
2390
+ type: params[paramName],
2391
+ raw: undefined,
2392
+ jsdoc: true
2393
+ };
2394
+ }
2395
+ var tagged = tagType(setComment, '@type', expandType);
2396
+ if (tagged) {
2397
+ return {
2398
+ type: tagged.type,
2399
+ raw: tagged.raw,
2400
+ jsdoc: true
2401
+ };
2402
+ }
2403
+ return {
2404
+ type: 'any',
2405
+ raw: undefined,
2406
+ jsdoc: false
2407
+ };
2408
+ }
2409
+ /**
2410
+ * Harvests the instance shape of a class: field declarations (JSDoc wins,
2411
+ * else inferred), constructor `this.x` writes (field site wins conflicts),
2412
+ * methods as `Function`, getter/setter pairs as writable and getter-only as
2413
+ * readonly. Statics, privates and dynamic keys are skipped.
2414
+ * @param {*} node - Babel ClassDeclaration node.
2415
+ * @param {object} opts - Options with expandType and warn.
2416
+ * @param {Function} opts.expandType - String type to structured type.
2417
+ * @param {Function} opts.warn - Transpile-time warning function.
2418
+ * @returns {{name: string, shape: object}|undefined} Class name plus object shape.
2419
+ */
2420
+ function harvestClassShape(node, _ref) {
2421
+ var expandType = _ref.expandType,
2422
+ warn = _ref.warn;
2423
+ var id = node === null || node === void 0 ? void 0 : node.id;
2424
+ if (!id || id.type !== 'Identifier' || !id.name) {
1352
2425
  return;
1353
2426
  }
1354
- var lines = value.split('\n');
1355
- var lastTypedef;
1356
- var _iterator = _createForOfIteratorHelper(lines),
1357
- _step;
2427
+ var className = id.name;
2428
+ var ctx = {
2429
+ fields: {},
2430
+ gets: {},
2431
+ sets: {},
2432
+ expandType: expandType,
2433
+ warn: warn,
2434
+ className: className
2435
+ };
2436
+ var ctor = null;
2437
+ var _iterator7 = _createForOfIteratorHelper(node.body.body),
2438
+ _step7;
1358
2439
  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();
2440
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
2441
+ var el = _step7.value;
2442
+ if (el.type === 'ClassPrivateProperty' || el.type === 'ClassPrivateMethod') {
2443
+ continue;
1364
2444
  }
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;
2445
+ var _name3 = keyName(el.key, el.computed);
2446
+ if (_name3 === undefined) {
2447
+ continue;
2448
+ }
2449
+ if (el.type === 'ClassProperty' || el.type === 'PropertyDefinition') {
2450
+ var _declared$type3;
2451
+ if (el.static || el.declare) {
2452
+ continue;
1376
2453
  }
1377
- } else if (line.startsWith('@property')) {
1378
- var _lastTypedef;
1379
- // class @property
1380
- if (!lastTypedef) {
2454
+ var comment = lastBlockComment(el);
2455
+ var declared = tagType(comment, '@type', expandType);
2456
+ var inferred = el.value ? inferTypeFromDefault$1(unwrap(el.value)) : undefined;
2457
+ var _type = (_declared$type3 = declared === null || declared === void 0 ? void 0 : declared.type) !== null && _declared$type3 !== void 0 ? _declared$type3 : inferred;
2458
+ if (_type === undefined) {
1381
2459
  continue;
1382
2460
  }
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);
2461
+ record(ctx.fields, _name3, {
2462
+ type: _type,
2463
+ rank: declared ? FIELD_JSDOC : FIELD_INFER,
2464
+ jsdoc: declared !== undefined,
2465
+ raw: declared === null || declared === void 0 ? void 0 : declared.raw,
2466
+ readonly: el.readonly === true || (comment ? /@readonly(?![\w])/.test(comment) : false),
2467
+ optional: el.optional === true
2468
+ }, warn, className);
2469
+ } else if (el.type === 'ClassMethod' || el.type === 'ClassPrivateMethod') {
2470
+ if (el.static) {
2471
+ continue;
2472
+ }
2473
+ if (el.kind === 'constructor') {
2474
+ ctor = el;
2475
+ } else if (el.kind === 'method') {
2476
+ var _ctx$fields$_name;
2477
+ ctx.fields[_name3] = (_ctx$fields$_name = ctx.fields[_name3]) !== null && _ctx$fields$_name !== void 0 ? _ctx$fields$_name : {
2478
+ type: 'Function',
2479
+ rank: FIELD_INFER,
2480
+ jsdoc: false
2481
+ };
2482
+ } else if (el.kind === 'get') {
2483
+ ctx.gets[_name3] = el;
2484
+ } else if (el.kind === 'set') {
2485
+ ctx.sets[_name3] = el;
1398
2486
  }
1399
- } else if (line.startsWith('@callback')) {
1400
- var _name2 = line.substring(9).trim();
1401
- typedefs[_name2] = 'Function';
1402
2487
  }
1403
2488
  }
1404
2489
  } catch (err) {
1405
- _iterator.e(err);
2490
+ _iterator7.e(err);
1406
2491
  } finally {
1407
- _iterator.f();
2492
+ _iterator7.f();
2493
+ }
2494
+ for (var _i = 0, _Object$keys = Object.keys(ctx.gets); _i < _Object$keys.length; _i++) {
2495
+ var _tagType, _ref2, _getType$type, _getType$raw;
2496
+ var name = _Object$keys[_i];
2497
+ var get = ctx.gets[name];
2498
+ var set = ctx.sets[name];
2499
+ var getComment = lastBlockComment(get);
2500
+ var getType = (_tagType = tagType(getComment, '@type', expandType)) !== null && _tagType !== void 0 ? _tagType : tagType(getComment, '@returns', expandType);
2501
+ var setInfo = set ? setterType(set, expandType) : undefined;
2502
+ 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';
2503
+ record(ctx.fields, name, {
2504
+ type: type,
2505
+ rank: getType || setInfo !== null && setInfo !== void 0 && setInfo.jsdoc ? FIELD_JSDOC : FIELD_INFER,
2506
+ jsdoc: Boolean(getType || (setInfo === null || setInfo === void 0 ? void 0 : setInfo.jsdoc)),
2507
+ 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,
2508
+ readonly: !set
2509
+ }, warn, className);
2510
+ }
2511
+ for (var _i2 = 0, _Object$keys2 = Object.keys(ctx.sets); _i2 < _Object$keys2.length; _i2++) {
2512
+ var _name = _Object$keys2[_i2];
2513
+ if (ctx.gets[_name]) {
2514
+ continue;
2515
+ }
2516
+ var _setInfo = setterType(ctx.sets[_name], expandType);
2517
+ record(ctx.fields, _name, {
2518
+ type: _setInfo.type,
2519
+ rank: _setInfo.jsdoc ? FIELD_JSDOC : FIELD_INFER,
2520
+ jsdoc: _setInfo.jsdoc,
2521
+ raw: _setInfo.raw
2522
+ }, warn, className);
1408
2523
  }
2524
+ if (ctor && ctor.body) {
2525
+ var _iterator8 = _createForOfIteratorHelper(ctor.body.body),
2526
+ _step8;
2527
+ try {
2528
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
2529
+ var st = _step8.value;
2530
+ walkStatement(st, false, ctx);
2531
+ }
2532
+ } catch (err) {
2533
+ _iterator8.e(err);
2534
+ } finally {
2535
+ _iterator8.f();
2536
+ }
2537
+ }
2538
+ var properties = {};
2539
+ for (var _i3 = 0, _Object$keys3 = Object.keys(ctx.fields); _i3 < _Object$keys3.length; _i3++) {
2540
+ var _name2 = _Object$keys3[_i3];
2541
+ properties[_name2] = entryToType(ctx.fields[_name2]);
2542
+ }
2543
+ if (!Object.keys(properties).length) {
2544
+ return;
2545
+ }
2546
+ return {
2547
+ name: className,
2548
+ shape: {
2549
+ type: 'object',
2550
+ properties: properties
2551
+ }
2552
+ };
1409
2553
  }
1410
2554
 
1411
2555
  /**
@@ -1454,101 +2598,6 @@
1454
2598
  return false;
1455
2599
  }
1456
2600
 
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
2601
  /**
1553
2602
  * @param {string} src - JSDoc comment of the setter.
1554
2603
  * @param {Function} expandType - The expandType function.
@@ -1583,9 +2632,6 @@
1583
2632
  var expandType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : expandTypeDepFree;
1584
2633
  var regexTemplateTyped = /@template \{(.*?)\} ([a-zA-Z0-9_$=]+)/g;
1585
2634
  var matches = _toConsumableArray(src.matchAll(regexTemplateTyped));
1586
- if (!matches.length) {
1587
- return;
1588
- }
1589
2635
  /** @type {Record<string, ExpandTypeReturnType>} */
1590
2636
  var templates = Object.create(null);
1591
2637
  matches.forEach(function (_) {
@@ -1593,6 +2639,40 @@
1593
2639
  var name = _[2].trim();
1594
2640
  templates[name] = type;
1595
2641
  });
2642
+ // `@template [A=X]` defaults: constraint X under name A.
2643
+ var regexTemplateDefault = /@template \[([a-zA-Z0-9_$]+)=([^\]]+)\]/g;
2644
+ var _iterator = _createForOfIteratorHelper(src.matchAll(regexTemplateDefault)),
2645
+ _step;
2646
+ try {
2647
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2648
+ var match = _step.value;
2649
+ templates[match[1]] = expandType(match[2].trim());
2650
+ }
2651
+ // Bare `@template T`: unconstrained, stands in as any.
2652
+ } catch (err) {
2653
+ _iterator.e(err);
2654
+ } finally {
2655
+ _iterator.f();
2656
+ }
2657
+ var regexTemplateBare = /@template ([a-zA-Z0-9_$]+)(?![a-zA-Z0-9_$])/g;
2658
+ var _iterator2 = _createForOfIteratorHelper(src.matchAll(regexTemplateBare)),
2659
+ _step2;
2660
+ try {
2661
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2662
+ var _match = _step2.value;
2663
+ var name = _match[1];
2664
+ if (!(name in templates)) {
2665
+ templates[name] = 'any';
2666
+ }
2667
+ }
2668
+ } catch (err) {
2669
+ _iterator2.e(err);
2670
+ } finally {
2671
+ _iterator2.f();
2672
+ }
2673
+ if (!Object.keys(templates).length) {
2674
+ return;
2675
+ }
1596
2676
  return templates;
1597
2677
  }
1598
2678
 
@@ -1618,8 +2698,12 @@
1618
2698
  */
1619
2699
  /**
1620
2700
  * Recursively clones and simplifies a type for emission into source code.
1621
- * Strips empty `properties` and collapses empty `object` types to the bare
1622
- * string `'object'`. Numbers/booleans (literal types) pass through.
2701
+ * Empty `properties` are preserved (not deleted) and empty `object` types
2702
+ * are kept structured: `{type: 'object'}` without a `properties` key is the
2703
+ * `{}` literal (accepts every non-nullish value like TS), while
2704
+ * `{type: 'object', properties: {}}` is the `object` keyword (rejects
2705
+ * primitives). Collapsing either form would erase that distinction.
2706
+ * Numbers/booleans (literal types) pass through.
1623
2707
  * Non-destructive — the original type tree is never mutated.
1624
2708
  * @param {string | DocType | number | boolean} type - The type.
1625
2709
  * @returns {string | DocType | number | boolean} The simplified type.
@@ -1631,9 +2715,6 @@
1631
2715
  var out = _objectSpread2({}, type);
1632
2716
  if (out.properties) {
1633
2717
  out.properties = mapValues(out.properties, simplifyType);
1634
- if (out.type === 'object' && !Object.keys(out.properties).length) {
1635
- delete out.properties;
1636
- }
1637
2718
  }
1638
2719
  if (out.indexSignatures && Array.isArray(out.indexSignatures)) {
1639
2720
  out.indexSignatures = out.indexSignatures.map(simplifyType);
@@ -1660,9 +2741,6 @@
1660
2741
  if (out.type === 'typeof' && out.argument) {
1661
2742
  out.argument = simplifyType(out.argument);
1662
2743
  }
1663
- if (out.type === 'object' && !out.properties && !out.indexSignatures && !out.optional) {
1664
- return 'object';
1665
- }
1666
2744
  return out;
1667
2745
  }
1668
2746
 
@@ -3934,6 +5012,8 @@
3934
5012
  });
3935
5013
  /** @type {Record<string, object>} */
3936
5014
  _defineProperty(_assertThisInitialized(_this), "typedefs", {});
5015
+ /** @type {Record<string, string[]>} */
5016
+ _defineProperty(_assertThisInitialized(_this), "typedefTemplates", {});
3937
5017
  /** @type {string[]} */
3938
5018
  _defineProperty(_assertThisInitialized(_this), "addLaterImportNamespaceSpecifier", []);
3939
5019
  _this.forceCurly = forceCurly;
@@ -3996,6 +5076,21 @@
3996
5076
  var id_ = this.toSource(id);
3997
5077
  var out = _get(_getPrototypeOf(Asserter.prototype), "ClassDeclaration", this).call(this, node);
3998
5078
  out += "".concat(this.spaces, "registerClass(").concat(id_, ");");
5079
+ var harvested = harvestClassShape(node, {
5080
+ expandType: this.expandType,
5081
+ warn: this.warn.bind(this)
5082
+ });
5083
+ if (harvested) {
5084
+ // Hand-written typedefs win: emitting a second registerTypedef for the
5085
+ // same name would be last-wins deterministic but noisy, so the harvest
5086
+ // step skips names already present in this.typedefs (populated in File).
5087
+ if (this.typedefs[harvested.name]) {
5088
+ this.warn("harvestClassShape: skipping harvested shape for '".concat(harvested.name, "', hand-written typedef wins"));
5089
+ } else {
5090
+ var json = simplifyTypeToSource(harvested.shape);
5091
+ out += "\n".concat(this.spaces, "registerTypedef('").concat(harvested.name, "', ").concat(json, ");");
5092
+ }
5093
+ }
3999
5094
  return out;
4000
5095
  }
4001
5096
  /**
@@ -4910,7 +6005,7 @@
4910
6005
  for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
4911
6006
  var comment = _step5.value;
4912
6007
  var warn = this.warn.bind(this);
4913
- parseJSDocTypedef(this.typedefs, warn, comment, this.expandType);
6008
+ parseJSDocTypedef(this.typedefs, this.typedefTemplates, warn, comment, this.expandType);
4914
6009
  }
4915
6010
  } catch (err) {
4916
6011
  _iterator5.e(err);
@@ -4923,7 +6018,8 @@
4923
6018
  for (var name in this.typedefs) {
4924
6019
  var typedef = this.typedefs[name];
4925
6020
  var json = simplifyTypeToSource(typedef);
4926
- out += "registerTypedef('".concat(name, "', ").concat(json, ");\n");
6021
+ var params = this.typedefTemplates[name];
6022
+ 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
6023
  }
4928
6024
  var code = this.toSource(program) + '\n';
4929
6025
  out += code;
@@ -5214,7 +6310,13 @@
5214
6310
  {
5215
6311
  var name = toSourceBabelTS(node.typeName);
5216
6312
  if (!node.typeParameters) {
5217
- // console.log(`node.typeName.name=${node.typeName.name} name=${name}`, node);
6313
+ // Bare `Object` is the empty object type, matching expandType().
6314
+ if (name === 'Object') {
6315
+ return {
6316
+ type: 'object',
6317
+ properties: {}
6318
+ };
6319
+ }
5218
6320
  // Bare reference: Identifier gives name, TSQualifiedName gives dotted path.
5219
6321
  return name;
5220
6322
  }
@@ -5275,6 +6377,8 @@
5275
6377
  }
5276
6378
  case 'TSStringKeyword':
5277
6379
  return 'string';
6380
+ case 'TSSymbolKeyword':
6381
+ return 'symbol';
5278
6382
  case 'TSNumberKeyword':
5279
6383
  return 'number';
5280
6384
  case 'TSIntersectionType':
@@ -5356,6 +6460,8 @@
5356
6460
  case 'ParenthesizedType':
5357
6461
  // fall-through for parentheses
5358
6462
  return toSourceBabelTS(node.type);
6463
+ case 'TSParenthesizedType':
6464
+ return toSourceBabelTS(node.typeAnnotation);
5359
6465
  case 'LastTypeNode':
5360
6466
  return toSourceBabelTS(node.qualifier);
5361
6467
  case 'TSTypeQuery':
@@ -5364,11 +6470,129 @@
5364
6470
  type: 'typeof',
5365
6471
  argument: argument
5366
6472
  };
6473
+ case 'TSConditionalType':
6474
+ {
6475
+ var checkType = toSourceBabelTS(node.checkType);
6476
+ var extendsType = toSourceBabelTS(node.extendsType);
6477
+ var trueType = toSourceBabelTS(node.trueType);
6478
+ var falseType = toSourceBabelTS(node.falseType);
6479
+ return {
6480
+ type: 'condition',
6481
+ checkType: checkType,
6482
+ extendsType: extendsType,
6483
+ trueType: trueType,
6484
+ falseType: falseType
6485
+ };
6486
+ }
6487
+ case 'TSIndexedAccessType':
6488
+ {
6489
+ var index = toSourceBabelTS(node.indexType);
6490
+ var object = toSourceBabelTS(node.objectType);
6491
+ return {
6492
+ type: 'indexedAccess',
6493
+ index: index,
6494
+ object: object
6495
+ };
6496
+ }
6497
+ case 'TSMappedType':
6498
+ {
6499
+ var param = node.typeParameter;
6500
+ var nameNode = param === null || param === void 0 ? void 0 : param.name;
6501
+ var element = typeof nameNode === 'string' ? nameNode : toSourceBabelTS(nameNode);
6502
+ var iterable = toSourceBabelTS(param === null || param === void 0 ? void 0 : param.constraint);
6503
+ var result = toSourceBabelTS(node.typeAnnotation);
6504
+ var out = {
6505
+ type: 'mapping',
6506
+ iterable: iterable,
6507
+ element: element,
6508
+ result: result
6509
+ };
6510
+ if (node.nameType) {
6511
+ out.nameType = toSourceBabelTS(node.nameType);
6512
+ }
6513
+ if (node.optional !== undefined && node.optional !== false && node.optional !== null) {
6514
+ out.question = node.optional === '-' ? '-' : node.optional === '+' ? '+' : '?';
6515
+ }
6516
+ if (node.readonly !== undefined && node.readonly !== false && node.readonly !== null) {
6517
+ out.readonly = node.readonly === '-' ? '-' : node.readonly === '+' ? '+' : 'readonly';
6518
+ }
6519
+ return out;
6520
+ }
6521
+ case 'TSTypeAnnotation':
6522
+ return toSourceBabelTS(node.typeAnnotation);
6523
+ case 'TSTypeLiteral':
6524
+ {
6525
+ var _node$members;
6526
+ var _properties = {};
6527
+ var indexSignatures;
6528
+ var _iterator = _createForOfIteratorHelper((_node$members = node.members) !== null && _node$members !== void 0 ? _node$members : []),
6529
+ _step;
6530
+ try {
6531
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
6532
+ var member = _step.value;
6533
+ if (member.type === 'TSIndexSignature') {
6534
+ var _indexSignatures;
6535
+ indexSignatures = (_indexSignatures = indexSignatures) !== null && _indexSignatures !== void 0 ? _indexSignatures : [];
6536
+ indexSignatures.push(toSourceBabelTS(member));
6537
+ } else if (member.type === 'TSPropertySignature') {
6538
+ var _name = toSourceBabelTS(member.key);
6539
+ var type = toSourceBabelTS(member.typeAnnotation);
6540
+ if (member.optional) {
6541
+ if (type && _typeof(type) === 'object') type.optional = true;else type = {
6542
+ type: type,
6543
+ optional: true
6544
+ };
6545
+ }
6546
+ if (member.readonly) {
6547
+ if (type && _typeof(type) === 'object') type.readonly = true;else type = {
6548
+ type: type,
6549
+ readonly: true
6550
+ };
6551
+ }
6552
+ _properties[_name] = type;
6553
+ } else {
6554
+ console.warn('TSTypeLiteral: unhandled member', member.type);
6555
+ }
6556
+ }
6557
+ } catch (err) {
6558
+ _iterator.e(err);
6559
+ } finally {
6560
+ _iterator.f();
6561
+ }
6562
+ var ret = {
6563
+ type: 'object'
6564
+ };
6565
+ if (Object.keys(_properties).length) {
6566
+ ret.properties = _properties;
6567
+ }
6568
+ if (indexSignatures) {
6569
+ ret.indexSignatures = indexSignatures;
6570
+ }
6571
+ return ret;
6572
+ }
6573
+ case 'TSIndexSignature':
6574
+ {
6575
+ var _node$parameters;
6576
+ var indexType = toSourceBabelTS(node.typeAnnotation);
6577
+ var indexParameters = ((_node$parameters = node.parameters) !== null && _node$parameters !== void 0 ? _node$parameters : []).map(toSourceBabelTS);
6578
+ return {
6579
+ type: 'indexSignature',
6580
+ indexType: indexType,
6581
+ indexParameters: indexParameters
6582
+ };
6583
+ }
5367
6584
  case 'TSTypeOperator':
5368
6585
  if (node.operator === 'readonly') {
5369
6586
  // readonly erased at runtime, same shape as the inner type.
5370
6587
  return toSourceBabelTS(node.typeAnnotation);
5371
6588
  }
6589
+ if (node.operator === 'keyof') {
6590
+ var keyofArg = toSourceBabelTS(node.typeAnnotation);
6591
+ return {
6592
+ type: 'keyof',
6593
+ argument: keyofArg
6594
+ };
6595
+ }
5372
6596
  console.warn('unimplemented TSTypeOperator', node.operator);
5373
6597
  return 'any';
5374
6598
  case 'TSQualifiedName':
@@ -5501,6 +6725,8 @@
5501
6725
  _defineProperty(this, "parents", []);
5502
6726
  /** @type {Record<string, object>} */
5503
6727
  _defineProperty(this, "typedefs", {});
6728
+ /** @type {Record<string, string[]>} */
6729
+ _defineProperty(this, "typedefTemplates", {});
5504
6730
  /** @type {import('./parseJSDoc.js').ExpandType} */
5505
6731
  this.expandType = options.expandType || expandTypeDepFree;
5506
6732
  }
@@ -5521,7 +6747,7 @@
5521
6747
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
5522
6748
  var comment = _step.value;
5523
6749
  var warn = console.warn.bind(console);
5524
- parseJSDocTypedef(this.typedefs, warn, comment, this.expandType);
6750
+ parseJSDocTypedef(this.typedefs, this.typedefTemplates, warn, comment, this.expandType);
5525
6751
  }
5526
6752
  } catch (err) {
5527
6753
  _iterator.e(err);