typenative 0.1.1 → 0.1.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.
package/bin/transpiler.js CHANGED
@@ -263,6 +263,7 @@ const importedPackages = new Set();
263
263
  let outsideNodes = [];
264
264
  const classNames = new Set();
265
265
  let promiseResolveName = '';
266
+ let promiseRejectName = '';
266
267
  // Go keywords that cannot be used as identifiers
267
268
  const dangerousNames = new Set([
268
269
  'main',
@@ -396,6 +397,11 @@ function defaultParseFunction(_code) {
396
397
  // AST carries no parent pointers, but the transpiler walks up via node.parent.
397
398
  function parseSource(code) {
398
399
  const sourceFile = parseFunction(code);
400
+ stripThisParameters(sourceFile);
401
+ lowerIndexSignatures(sourceFile);
402
+ lowerClassExpressions(sourceFile);
403
+ lowerWeakCollections(sourceFile);
404
+ lowerGlobalThis(sourceFile);
399
405
  linkParents(sourceFile, undefined);
400
406
  for (const stmt of sourceFile.statements ?? []) {
401
407
  if (!stmt.name || !isIdentifier(stmt.name))
@@ -415,6 +421,83 @@ function parseSource(code) {
415
421
  }
416
422
  return sourceFile;
417
423
  }
424
+ // `this: T` parameters only type `this`; they are not real parameters
425
+ function stripThisParameters(node) {
426
+ if (Array.isArray(node.parameters)) {
427
+ node.parameters = node.parameters.filter((p) => !(isIdentifier(p.name) && p.name.text === 'this'));
428
+ }
429
+ for (const child of childNodes(node))
430
+ stripThisParameters(child);
431
+ }
432
+ // { [k: K]: V } and { [K in string]: V } → Record<K, V>; an interface that is
433
+ // only an index signature becomes an alias of that Record
434
+ function lowerIndexSignatures(node) {
435
+ for (const child of childNodes(node))
436
+ lowerIndexSignatures(child);
437
+ const keyKind = node.typeParameter?.constraint?.kind;
438
+ if (node.kind === 'MappedType' && (keyKind === 'StringKeyword' || keyKind === 'NumberKeyword')) {
439
+ const keyType = { kind: keyKind };
440
+ makeRecordReference(node, keyType, node.type);
441
+ return;
442
+ }
443
+ const members = node.members ?? [];
444
+ if (members.length !== 1 || members[0].kind !== 'IndexSignature')
445
+ return;
446
+ const signature = members[0];
447
+ if (node.kind === 'TypeLiteral')
448
+ makeRecordReference(node, signature.parameters[0].type, signature.type);
449
+ if (node.kind === 'InterfaceDeclaration') {
450
+ const record = { kind: 'TypeLiteral' };
451
+ makeRecordReference(record, signature.parameters[0].type, signature.type);
452
+ delete node.members;
453
+ delete node.heritageClauses;
454
+ node.kind = 'TypeAliasDeclaration';
455
+ node.type = record;
456
+ }
457
+ }
458
+ // Turns node into the type reference Record<keyType, valueType>
459
+ function makeRecordReference(node, keyType, valueType) {
460
+ for (const key of Object.keys(node))
461
+ if (key !== 'parent')
462
+ delete node[key];
463
+ const typeName = { kind: 'Identifier', text: 'Record' };
464
+ node.kind = 'TypeReference';
465
+ node.typeName = typeName;
466
+ node.typeArguments = [keyType, valueType];
467
+ }
468
+ // const Point = class { … } → class Point { … } (the variable names the class)
469
+ function lowerClassExpressions(node) {
470
+ for (const child of childNodes(node))
471
+ lowerClassExpressions(child);
472
+ if (!Array.isArray(node.statements))
473
+ return;
474
+ node.statements = node.statements.map((statement) => {
475
+ const declarations = statement.kind === 'VariableStatement' ? (statement.declarationList?.declarations ?? []) : [];
476
+ const initializer = declarations.length === 1 ? declarations[0].initializer : undefined;
477
+ if (initializer?.kind !== 'ClassExpression' || !isIdentifier(declarations[0].name))
478
+ return statement;
479
+ return { ...initializer, kind: 'ClassDeclaration', name: declarations[0].name, modifiers: statement.modifiers };
480
+ });
481
+ }
482
+ // WeakMap / WeakSet → Map / Set: same API; weakness is not observable
483
+ function lowerWeakCollections(node) {
484
+ for (const child of childNodes(node))
485
+ lowerWeakCollections(child);
486
+ const name = node.kind === 'NewExpression' ? node.expression : node.kind === 'TypeReference' ? node.typeName : undefined;
487
+ if (isIdentifier(name) && (name.text === 'WeakMap' || name.text === 'WeakSet'))
488
+ name.text = name.text.slice(4);
489
+ }
490
+ // globalThis.x (typed) is the global x itself; (globalThis as any) stays a dynamic object
491
+ function lowerGlobalThis(node) {
492
+ for (const key of Object.keys(node)) {
493
+ const child = node[key];
494
+ if (isPropertyAccessExpression(child) && isIdentifier(child.expression) && child.expression.text === 'globalThis') {
495
+ node[key] = child.name;
496
+ }
497
+ }
498
+ for (const child of childNodes(node))
499
+ lowerGlobalThis(child);
500
+ }
418
501
  function linkParents(node, parent) {
419
502
  node.parent = parent;
420
503
  for (const key of Object.keys(node)) {
@@ -542,6 +625,8 @@ export function transpileToNative(code, options) {
542
625
  const drains = [];
543
626
  if (usedHelpers.has('asyncWait'))
544
627
  drains.push('__tnWaitGroup.Wait()');
628
+ if (usedHelpers.has('timerWait'))
629
+ drains.unshift('TnRunMicrotasks()');
545
630
  if (usedHelpers.has('timerWait'))
546
631
  drains.push('__tnTimers.Wait()');
547
632
  const drainCode = drains.length > 0 ? `\n\t${drains.join('\n\t')}` : '';
@@ -600,6 +685,10 @@ function visit(node, options = {}) {
600
685
  .filter((n) => !!n)
601
686
  .join(options.inline ? '' : '\n\t');
602
687
  }
688
+ else if (isIdentifier(node) && node.text === 'globalThis') {
689
+ useHelper('globalThis');
690
+ return 'TnGlobalThis';
691
+ }
603
692
  else if (isIdentifier(node)) {
604
693
  if (isFieldNameIdentifier(node))
605
694
  return goFieldName(node.text);
@@ -640,6 +729,18 @@ function visit(node, options = {}) {
640
729
  else if (isTemplateExpression(node)) {
641
730
  return visitTemplateExpression(node);
642
731
  }
732
+ else if (node.kind === 'TaggedTemplateExpression' && isStringRawTag(node.tag)) {
733
+ // String.raw`a\n${x}`: the raw source text with the values interpolated
734
+ const template = node.template;
735
+ const raw = (part) => toGoStringLiteral(part.rawText ?? part.text);
736
+ if (template.kind === 'NoSubstitutionTemplateLiteral')
737
+ return raw(template);
738
+ const pieces = [raw(template.head)];
739
+ for (const span of template.templateSpans ?? []) {
740
+ pieces.push(jsStringOf(visit(span.expression), inferExpressionType(span.expression)), raw(span.literal));
741
+ }
742
+ return `(${pieces.join(' + ')})`;
743
+ }
643
744
  else if (node.kind === 'TaggedTemplateExpression') {
644
745
  // tag`x${2}` → tag(["x", ""], 2)
645
746
  const tag = visit(node.tag);
@@ -684,7 +785,7 @@ function visit(node, options = {}) {
684
785
  // backtracking engine — both live in the TnRegex wrapper
685
786
  if (needsStatefulRegex(pattern, flags)) {
686
787
  useHelper('fancyRegex');
687
- return `TnRegexCompile("${goPattern}", ${flags.includes('g') ? 'true' : 'false'})`;
788
+ return `TnRegexCompile("${goPattern}", "${flags}", ${toGoStringLiteral(pattern)})`;
688
789
  }
689
790
  importedPackages.add('regexp');
690
791
  const goFlags = jsRegexFlagsToGo(flags);
@@ -735,16 +836,19 @@ function visit(node, options = {}) {
735
836
  return `${visit(node.expression)}[int(${visit(node.argumentExpression)})]`;
736
837
  }
737
838
  else if (isPropertyAccessExpression(node)) {
839
+ const regexProperty = visitRegexProperty(node);
840
+ if (regexProperty)
841
+ return regexProperty;
738
842
  // import.meta.url — the file URL of the running program
739
843
  if (node.expression.kind === 'MetaProperty' && node.name.text === 'url') {
740
844
  useHelper('moduleUrl');
741
845
  return 'TnModuleURL()';
742
846
  }
743
- if (isDynamicValue(node.expression) && node.name.text === 'length' && !isCallee(node)) {
847
+ if (hasDynamicProperties(node.expression) && node.name.text === 'length' && !isCallee(node)) {
744
848
  useHelper('dynamic');
745
849
  return `TnLength(${visit(node.expression)})`;
746
850
  }
747
- if (isDynamicValue(node.expression) && !isCallee(node) && !isProcessEnv(node.expression)) {
851
+ if (hasDynamicProperties(node.expression) && !isCallee(node) && !isProcessEnv(node.expression)) {
748
852
  useHelper('dynamic');
749
853
  return `TnGet(${visit(node.expression)}, "${node.name.text}")`;
750
854
  }
@@ -953,6 +1057,11 @@ function visit(node, options = {}) {
953
1057
  if (referenceArrays.has(node.name.text) && type.startsWith('[]')) {
954
1058
  type = variableGoTypes.get(node.name.text);
955
1059
  }
1060
+ // const v = undefined / void 0: Go cannot infer a type from nil
1061
+ if (type === ':' && inferredType === 'nil') {
1062
+ type = 'interface{}';
1063
+ setVarGoType(node.name.text, 'interface{}');
1064
+ }
956
1065
  }
957
1066
  let initializer = node.initializer ? `= ${visit(node.initializer)}` : '';
958
1067
  // Wrap non-nil values assigned to nullable primitive pointer types
@@ -981,6 +1090,13 @@ function visit(node, options = {}) {
981
1090
  return initializer ? `${name} ${initializer}` : '';
982
1091
  }
983
1092
  const declName = isIdentifier(node.name) ? getSafeName(node.name.text) : visit(node.name);
1093
+ // const f = (…) => … f(…) … / const id = setInterval(() => clearInterval(id)):
1094
+ // a Go closure can't see the variable in its own :=, so declare first, then assign
1095
+ if (type === ':' &&
1096
+ isIdentifier(node.name) &&
1097
+ containsNode(node.initializer, (n) => isIdentifier(n) && n.text === node.name.text && !isFieldNameIdentifier(n))) {
1098
+ return `var ${declName} ${inferExpressionType(node.initializer)}\n\t\t${declName} ${initializer}`;
1099
+ }
984
1100
  return `${type === ':' ? '' : 'var '}${declName} ${type}${type === ':' ? '' : ' '}${initializer}`;
985
1101
  }
986
1102
  else if (isCallExpression(node)) {
@@ -994,6 +1110,9 @@ function visit(node, options = {}) {
994
1110
  return visitNullablePrimitiveOptionalCall(node, visit(node.expression.expression), receiverType);
995
1111
  }
996
1112
  }
1113
+ const recordCall = visitRecordFunctionCall(node);
1114
+ if (recordCall)
1115
+ return recordCall;
997
1116
  const dynamicCall = visitDynamicMethodCall(node);
998
1117
  if (dynamicCall)
999
1118
  return dynamicCall;
@@ -1020,8 +1139,8 @@ function visit(node, options = {}) {
1020
1139
  const callback = visit((node.arguments ?? [])[0]);
1021
1140
  const delayNode = (node.arguments ?? [])[1];
1022
1141
  const delay = isNumericLiteral(delayNode) ? delayNode.text : visit(delayNode);
1023
- // Registered on the wait group so the process lives until it fires
1024
- return `__tnTimers.Add(1)\n\t\ttime.AfterFunc(${delay} * time.Millisecond, func() {\n\t\t\tdefer __tnTimers.Done()\n\t\t\t${callback.trim()}()\n\t\t})`;
1142
+ // Registered on the wait group so the process lives until it fires (or is cleared)
1143
+ return `TnSetTimeout(${delay ?? '0'}, func() { (${callback.trim()})() })`;
1025
1144
  }
1026
1145
  // p.then(cb) on a channel-based Promise: wait for the value, run the callback
1027
1146
  if (isPropertyAccessExpression(node.expression) &&
@@ -1075,6 +1194,12 @@ function visit(node, options = {}) {
1075
1194
  if (arrayHigherOrderCall) {
1076
1195
  return arrayHigherOrderCall;
1077
1196
  }
1197
+ const promiseCall = visitPromiseCall(node);
1198
+ if (promiseCall)
1199
+ return promiseCall;
1200
+ const objectCall = visitObjectCall(node);
1201
+ if (objectCall)
1202
+ return objectCall;
1078
1203
  const caller = visit(node.expression);
1079
1204
  const safeCaller = getSafeName(caller);
1080
1205
  const typeArgs = getTypeArguments((node.typeArguments ?? []));
@@ -1086,6 +1211,7 @@ function visit(node, options = {}) {
1086
1211
  if (isPropertyAccessExpression(node.expression)) {
1087
1212
  objectType = resolveExpressionType(node.expression.expression);
1088
1213
  }
1214
+ currentCallArgTypes = (node.arguments ?? []).map((a) => inferExpressionType(a));
1089
1215
  currentReceiverGoType = isPropertyAccessExpression(node.expression)
1090
1216
  ? inferExpressionType(node.expression.expression)
1091
1217
  : undefined;
@@ -1106,6 +1232,15 @@ function visit(node, options = {}) {
1106
1232
  else if (isPostfixUnaryExpression(node)) {
1107
1233
  return visitIncrementOrDecrement(node, false);
1108
1234
  }
1235
+ else if (node.kind === 'VoidExpression') {
1236
+ // void expr evaluates expr and gives undefined
1237
+ if (isNumericLiteral(node.expression))
1238
+ return 'nil';
1239
+ return `func() interface{} { _ = ${visit(node.expression)}; return nil }()`;
1240
+ }
1241
+ else if (node.kind === 'SatisfiesExpression') {
1242
+ return visit(node.expression);
1243
+ }
1109
1244
  else if (node.kind === 'DeleteExpression') {
1110
1245
  return visitDelete(node);
1111
1246
  }
@@ -1254,6 +1389,11 @@ function visit(node, options = {}) {
1254
1389
  return op === '==' ? eq : `!(${eq})`;
1255
1390
  }
1256
1391
  }
1392
+ // obj[key] = v on any
1393
+ if (op === '=' && isElementAccessExpression(node.left) && isDynamicValue(node.left.expression)) {
1394
+ useHelper('dynamic');
1395
+ return `TnSetIndex(${visit(node.left.expression)}, ${visit(node.left.argumentExpression)}, ${visit(node.right)})`;
1396
+ }
1257
1397
  if (op === '=' && isPropertyAccessExpression(node.left) && isDynamicValue(node.left.expression)) {
1258
1398
  useHelper('dynamic');
1259
1399
  return `TnSet(${visit(node.left.expression)}, "${node.left.name.text}", ${visit(node.right)})`;
@@ -1328,6 +1468,10 @@ function visit(node, options = {}) {
1328
1468
  return '';
1329
1469
  }
1330
1470
  else if (isAwaitExpression(node)) {
1471
+ if (inferExpressionType(node.expression)?.startsWith('chan ')) {
1472
+ useHelper('promise');
1473
+ return `TnAwait(${visit(node.expression)})`;
1474
+ }
1331
1475
  return `<-${visit(node.expression)}`;
1332
1476
  }
1333
1477
  else if (isVariableDeclarationList(node)) {
@@ -1342,11 +1486,15 @@ function visit(node, options = {}) {
1342
1486
  asyncFunctionNames.has(exprStmt.expression.text) &&
1343
1487
  !emittingModuleFile) {
1344
1488
  useHelper('asyncWait');
1345
- return `__tnWaitGroup.Add(1)\n\t\tgo func() {\n\t\t\tdefer __tnWaitGroup.Done()\n\t\t\t<-${getSafeName(exprStmt.expression.text)}()\n\t\t}();\n\t`;
1489
+ useHelper('promise');
1490
+ return `__tnWaitGroup.Add(1)\n\t\tgo func() {\n\t\t\tdefer __tnWaitGroup.Done()\n\t\t\tTnAwait(${getSafeName(exprStmt.expression.text)}())\n\t\t}();\n\t`;
1346
1491
  }
1347
1492
  return visit(node.expression) + (options.inline ? '' : ';\n\t');
1348
1493
  }
1349
1494
  else if (isForStatement(node)) {
1495
+ const multi = visitMultiForHeader(node);
1496
+ if (multi)
1497
+ return multi;
1350
1498
  return `for ${visit(node.initializer, { inline: true })}; ${node.condition ? toGoCondition(node.condition) : ''}; ${visit(node.incrementor, { inline: true })}${visitLoopBody(node.statement)}`;
1351
1499
  }
1352
1500
  else if (isForInStatement(node)) {
@@ -1489,6 +1637,9 @@ function visit(node, options = {}) {
1489
1637
  return `return ${value}` + terminator;
1490
1638
  }
1491
1639
  else if (isFunctionDeclaration(node) || isFunctionExpression(node)) {
1640
+ // overload signatures have no body: only the implementation is emitted
1641
+ if (!node.body)
1642
+ return '';
1492
1643
  if (options.addFunctionOutside) {
1493
1644
  outsideNodes.push(node);
1494
1645
  return '';
@@ -1535,7 +1686,7 @@ function visit(node, options = {}) {
1535
1686
  }
1536
1687
  if (!isBlock(node.body)) {
1537
1688
  // void-style calls (console.*, assert) emit statements, not values
1538
- if (isVoidStatementCall(node.body)) {
1689
+ if (isVoidStatementCall(node.body) || !inferredRetType) {
1539
1690
  return `func(${parameterInfo.signature})${returnType} { ${visit(node.body)}; }`;
1540
1691
  }
1541
1692
  return `func(${parameterInfo.signature})${returnType} { return ${visit(node.body)}; }`;
@@ -1936,6 +2087,12 @@ function visit(node, options = {}) {
1936
2087
  result += `var ${name}_${fieldName} ${fieldType}${initializer}\n\n`;
1937
2088
  }
1938
2089
  }
2090
+ // static { … } blocks run once at program start, after the static fields
2091
+ for (const member of node.members ?? []) {
2092
+ if (member.kind === 'ClassStaticBlockDeclaration' && member.body) {
2093
+ result += `func init() ${visit(member.body).trimEnd()}\n\n`;
2094
+ }
2095
+ }
1939
2096
  return result.trim();
1940
2097
  }
1941
2098
  else if (isNewExpression(node)) {
@@ -2018,8 +2175,12 @@ function visit(node, options = {}) {
2018
2175
  if (!typeName || typeName === 'interface{}') {
2019
2176
  return visitAnonymousStructLiteral(node);
2020
2177
  }
2021
- // Objects are pointers: the struct type is typeName without its *
2022
- const structName = typeName.replace(/^\*/, '');
2178
+ // Objects are pointers: the struct type is typeName without its *; generic
2179
+ // types are instantiated with the contextual type arguments (Box<number> → Box[float64])
2180
+ const typeArgs = isTypeReferenceNode(contextualType) && contextualType.typeName.text === typeName.replace(/^\*/, '')
2181
+ ? getTypeArguments(contextualType.typeArguments)
2182
+ : '';
2183
+ const structName = typeName.replace(/^\*/, '') + (typeName.endsWith(']') ? '' : typeArgs);
2023
2184
  // { ...base, x: 1 } → a copy of *base with fields set
2024
2185
  if ((node.properties ?? []).some((p) => p.kind === 'SpreadAssignment')) {
2025
2186
  const steps = (node.properties ?? []).map((p) => {
@@ -2035,7 +2196,7 @@ function visit(node, options = {}) {
2035
2196
  });
2036
2197
  return `func() *${structName} { var __obj ${structName}; ${steps.join('; ')}; return &__obj }()`;
2037
2198
  }
2038
- const fieldTypes = interfacePropertyTypes.get(structName) ?? classPropertyTypes.get(structName);
2199
+ const fieldTypes = typeArgs ? undefined : (interfacePropertyTypes.get(structName) ?? classPropertyTypes.get(structName));
2039
2200
  const properties = (node.properties ?? [])
2040
2201
  .map((p) => {
2041
2202
  if (isPropertyAssignment(p)) {
@@ -2137,7 +2298,7 @@ function toGoValueOfType(expr, goType) {
2137
2298
  return code;
2138
2299
  if (isBinaryExpression(expr) &&
2139
2300
  expr.operatorToken.kind === 'QuestionQuestionToken' &&
2140
- inferExpressionType(expr.left)?.startsWith('*')) {
2301
+ getNullishEmitType(expr) === goType) {
2141
2302
  return code;
2142
2303
  }
2143
2304
  if (inferExpressionType(expr) !== goType.slice(1))
@@ -2270,7 +2431,8 @@ function isNarrowableReference(node) {
2270
2431
  return false;
2271
2432
  if (isPropertyAccessExpression(parent) && parent.name === node)
2272
2433
  return false;
2273
- if (parent.name === node)
2434
+ // { x } reads the variable x
2435
+ if (parent.name === node && !isShorthandPropertyAssignment(parent))
2274
2436
  return false;
2275
2437
  if (isBinaryExpression(parent) && parent.left === node) {
2276
2438
  const op = parent.operatorToken.kind;
@@ -2533,6 +2695,71 @@ function getPropertyDeclarationType(member) {
2533
2695
  return getOptionalNodeType(member.type, !!member.questionToken);
2534
2696
  return toFieldGoType(inferExpressionType(member.initializer));
2535
2697
  }
2698
+ // Sorts in place and returns the array; without a comparator, compares as strings (like JS)
2699
+ function sortArray(obj, args) {
2700
+ importedPackages.add('sort');
2701
+ const arrayType = currentReceiverGoType?.startsWith('[]') ? currentReceiverGoType : '[]interface{}';
2702
+ let less = `(${args[0]})(__s[i], __s[j]) < 0`;
2703
+ if (!args[0]) {
2704
+ importedPackages.add('fmt');
2705
+ less = `fmt.Sprintf("%v", __s[i]) < fmt.Sprintf("%v", __s[j])`;
2706
+ }
2707
+ return `func() ${arrayType} { __s := ${obj}; sort.SliceStable(__s, func(i, j int) bool { return ${less} }); return __s }()`;
2708
+ }
2709
+ function mathCall(fn, args) {
2710
+ importedPackages.add('math');
2711
+ return `math.${fn}(${args.join(', ')})`;
2712
+ }
2713
+ // Go types of the arguments of the call being emitted (for call handlers)
2714
+ let currentCallArgTypes = [];
2715
+ // for headers with several declarations / updates: Go's parallel forms
2716
+ // (i, j := 0, 10 and i, j = i+1, j-1)
2717
+ function visitMultiForHeader(node) {
2718
+ const declarations = isVariableDeclarationList(node.initializer) ? (node.initializer.declarations ?? []) : [];
2719
+ const updates = [];
2720
+ const collect = (expr) => {
2721
+ if (!expr)
2722
+ return;
2723
+ if (isBinaryExpression(expr) && expr.operatorToken.kind === 'CommaToken') {
2724
+ collect(expr.left);
2725
+ collect(expr.right);
2726
+ }
2727
+ else
2728
+ updates.push(expr);
2729
+ };
2730
+ collect(node.incrementor);
2731
+ if (declarations.length < 2 && updates.length < 2)
2732
+ return undefined;
2733
+ let init = visit(node.initializer, { inline: true });
2734
+ if (declarations.length >= 2) {
2735
+ for (const d of declarations) {
2736
+ if (isIdentifier(d.name) && d.initializer)
2737
+ registerLocalVariable(d.name.text, inferExpressionType(d.initializer));
2738
+ }
2739
+ init = `${declarations.map((d) => visit(d.name)).join(', ')} := ${declarations.map((d) => (d.initializer ? visit(d.initializer) : 'nil')).join(', ')}`;
2740
+ }
2741
+ let post = visit(node.incrementor, { inline: true });
2742
+ if (updates.length >= 2) {
2743
+ const targets = [];
2744
+ const values = [];
2745
+ for (const u of updates) {
2746
+ if ((isPrefixUnaryExpression(u) || isPostfixUnaryExpression(u)) && isIncrementOrDecrement(u)) {
2747
+ const target = visit(u.operand);
2748
+ targets.push(target);
2749
+ values.push(`${target} ${u.operator === 'PlusPlusToken' ? '+' : '-'} 1`);
2750
+ }
2751
+ else if (isBinaryExpression(u) && u.operatorToken.kind === 'EqualsToken') {
2752
+ targets.push(visit(u.left));
2753
+ values.push(visit(u.right));
2754
+ }
2755
+ else
2756
+ return undefined;
2757
+ }
2758
+ post = `${targets.join(', ')} = ${values.join(', ')}`;
2759
+ }
2760
+ const condition = node.condition ? toGoCondition(node.condition) : '';
2761
+ return `for ${init}; ${condition}; ${post}${visitLoopBody(node.statement)}`;
2762
+ }
2536
2763
  function isIncrementOrDecrement(node) {
2537
2764
  return node.operator === 'PlusPlusToken' || node.operator === 'MinusMinusToken';
2538
2765
  }
@@ -2598,6 +2825,15 @@ function getDictionaryValueType(node) {
2598
2825
  function isDynamicObjectLiteral(node) {
2599
2826
  if (isAnyContext(getContextualTypeNode(node)))
2600
2827
  return true;
2828
+ // { name: { … } } nested in a dynamic object (with no declared member type) is dynamic too
2829
+ const parent = node.parent;
2830
+ if (isPropertyAssignment(parent) &&
2831
+ parent.initializer === node &&
2832
+ isObjectLiteralExpression(parent.parent) &&
2833
+ !getContextualTypeNode(node) &&
2834
+ isDynamicObjectLiteral(parent.parent)) {
2835
+ return true;
2836
+ }
2601
2837
  return (node.properties ?? []).some((p) => p.kind === 'SpreadAssignment' && isDynamicValue(p.expression));
2602
2838
  }
2603
2839
  function getObjectLiteralGoType(node) {
@@ -2638,6 +2874,14 @@ function getObjectLiteralGoType(node) {
2638
2874
  function isDynamicValue(expr) {
2639
2875
  return inferExpressionType(expr) === 'interface{}';
2640
2876
  }
2877
+ // A value whose type is a type parameter (a: T in f<T>): its properties are read dynamically
2878
+ function isTypeParameterValue(expr) {
2879
+ const type = inferExpressionType(expr);
2880
+ return !!type && isUnboundGoType(type) && isTypeParameterInScope(type, expr);
2881
+ }
2882
+ function hasDynamicProperties(expr) {
2883
+ return isDynamicValue(expr) || isTypeParameterValue(expr);
2884
+ }
2641
2885
  // Whether an expression is written to (x = …, x += …, x++)
2642
2886
  function isAssignmentTarget(node) {
2643
2887
  const parent = node.parent;
@@ -2680,6 +2924,30 @@ function isAnyContext(typeNode) {
2680
2924
  }
2681
2925
  return false;
2682
2926
  }
2927
+ const JS_MAP_METHODS = new Set(['get', 'set', 'has', 'delete', 'clear', 'keys', 'values', 'entries', 'forEach']);
2928
+ // rec.fn(args) where rec is a Record/dictionary (an ordered map) holding functions.
2929
+ // Names of JS Map methods (get, set, …) are Map methods unless rec is typed as a Record.
2930
+ function getRecordFunctionCall(node) {
2931
+ if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression))
2932
+ return undefined;
2933
+ const receiver = node.expression.expression;
2934
+ const mapType = inferExpressionType(receiver);
2935
+ if (!mapType?.startsWith('*TnMap['))
2936
+ return undefined;
2937
+ if (!extractMapValueType(mapType).startsWith('func('))
2938
+ return undefined;
2939
+ if (JS_MAP_METHODS.has(node.expression.name.text) && !isRecordTypeNode(resolveTypeNode(getExpressionTypeNode(receiver)))) {
2940
+ return undefined;
2941
+ }
2942
+ return { receiver, mapType };
2943
+ }
2944
+ function visitRecordFunctionCall(node) {
2945
+ const call = getRecordFunctionCall(node);
2946
+ if (!call)
2947
+ return undefined;
2948
+ const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
2949
+ return `${visit(call.receiver)}.Get(${toGoStringLiteral(node.expression.name.text)})(${args})`;
2950
+ }
2683
2951
  function isRecordTypeNode(typeNode) {
2684
2952
  return (isTypeReferenceNode(typeNode) &&
2685
2953
  isIdentifier(typeNode.typeName) &&
@@ -2981,8 +3249,9 @@ function getContextualTypeNode(node) {
2981
3249
  return getExpressionTypeNode(parent.left);
2982
3250
  }
2983
3251
  }
2984
- if (isAsExpression(parent) || isTypeAssertionExpression(parent))
3252
+ if (isAsExpression(parent) || isTypeAssertionExpression(parent) || parent.kind === 'SatisfiesExpression') {
2985
3253
  return parent.type;
3254
+ }
2986
3255
  if ((isVariableDeclaration(parent) || isPropertyDeclaration(parent) || parent.kind === 'Parameter') &&
2987
3256
  parent.initializer === node) {
2988
3257
  return parent.type;
@@ -3012,6 +3281,14 @@ function getContextualTypeNode(node) {
3012
3281
  }
3013
3282
  return undefined;
3014
3283
  }
3284
+ // A type parameter of an enclosing function or class (T inside function f<T>)
3285
+ function isTypeParameterInScope(name, node) {
3286
+ for (let n = node; n; n = n.parent) {
3287
+ if ((n.typeParameters ?? []).some((tp) => tp.name?.text === name))
3288
+ return true;
3289
+ }
3290
+ return false;
3291
+ }
3015
3292
  // Element type of an array literal: from its contextual type (declared
3016
3293
  // variable/parameter/return type), else inferred from its first typed element
3017
3294
  function getArrayLiteralElementType(node) {
@@ -3019,7 +3296,7 @@ function getArrayLiteralElementType(node) {
3019
3296
  if (isArrayTypeNode(contextual)) {
3020
3297
  const elem = getType(contextual.elementType);
3021
3298
  // An unbound type parameter (call-site []T) resolves from the elements
3022
- if (!isUnboundGoType(elem))
3299
+ if (!isUnboundGoType(elem) || isTypeParameterInScope(elem, node))
3023
3300
  return elem;
3024
3301
  }
3025
3302
  if (contextual?.kind === 'TupleType')
@@ -3032,6 +3309,7 @@ function getArrayLiteralElementType(node) {
3032
3309
  if (!isUnboundGoType(elem))
3033
3310
  return elem;
3034
3311
  }
3312
+ let firstElementType;
3035
3313
  for (const element of node.elements ?? []) {
3036
3314
  if (isSpreadElement(element)) {
3037
3315
  const spreadType = inferExpressionType(element.expression);
@@ -3044,11 +3322,16 @@ function getArrayLiteralElementType(node) {
3044
3322
  }
3045
3323
  continue;
3046
3324
  }
3325
+ // mixed element types ([['a', 1]]) make an interface{} array
3047
3326
  const elementType = inferExpressionType(element);
3048
- if (elementType && elementType !== 'nil' && !isUnboundGoType(elementType))
3049
- return elementType;
3327
+ if (elementType && elementType !== 'nil' && !isUnboundGoType(elementType)) {
3328
+ if (firstElementType && firstElementType !== elementType)
3329
+ return 'interface{}';
3330
+ if (!firstElementType)
3331
+ firstElementType = elementType;
3332
+ }
3050
3333
  }
3051
- return 'interface{}';
3334
+ return firstElementType ?? 'interface{}';
3052
3335
  }
3053
3336
  // A Go type that is a generic type parameter of the enclosing function (T)
3054
3337
  // rather than a concrete type: unresolvable at the call site
@@ -3111,6 +3394,13 @@ function getTupleElementType(tuple) {
3111
3394
  function getTypeText(typeNode) {
3112
3395
  if (!typeNode)
3113
3396
  return ':';
3397
+ // Partial<T> etc. construct T
3398
+ if (isTypeReferenceNode(typeNode) &&
3399
+ isIdentifier(typeNode.typeName) &&
3400
+ UTILITY_TYPES.has(typeNode.typeName.text) &&
3401
+ typeNode.typeArguments?.length) {
3402
+ return getTypeText(typeNode.typeArguments[0]);
3403
+ }
3114
3404
  if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
3115
3405
  return typeNode.typeName.text;
3116
3406
  }
@@ -3187,8 +3477,10 @@ function visitTemplateExpression(node) {
3187
3477
  function isVoidStatementCall(expr) {
3188
3478
  if (!isCallExpression(expr))
3189
3479
  return false;
3190
- if (isIdentifier(expr.expression))
3191
- return expr.expression.text === 'assert';
3480
+ if (isIdentifier(expr.expression)) {
3481
+ const name = expr.expression.text;
3482
+ return name === 'assert' || (!!promiseResolveName && name === promiseResolveName) || (!!promiseRejectName && name === promiseRejectName);
3483
+ }
3192
3484
  return (isPropertyAccessExpression(expr.expression) &&
3193
3485
  isIdentifier(expr.expression.expression) &&
3194
3486
  expr.expression.expression.text === 'console');
@@ -3298,10 +3590,16 @@ function inferExpressionType(expr) {
3298
3590
  }
3299
3591
  if (isNumericLiteral(expr))
3300
3592
  return 'float64';
3593
+ if (expr.kind === 'VoidExpression')
3594
+ return 'nil';
3595
+ if (expr.kind === 'SatisfiesExpression')
3596
+ return inferExpressionType(expr.expression);
3301
3597
  if (expr.kind === 'BigIntLiteral')
3302
3598
  return '*tnbig.Int';
3303
3599
  if (expr.kind === 'TypeOfExpression')
3304
3600
  return 'string';
3601
+ if (expr.kind === 'TaggedTemplateExpression' && isStringRawTag(expr.tag))
3602
+ return 'string';
3305
3603
  if (isRegularExpressionLiteral(expr)) {
3306
3604
  const lastSlash = expr.text.lastIndexOf('/');
3307
3605
  if (needsStatefulRegex(expr.text.substring(1, lastSlash), expr.text.substring(lastSlash + 1))) {
@@ -3396,6 +3694,15 @@ function inferExpressionType(expr) {
3396
3694
  : isPropertyAccessExpression(expr.expression) && isIdentifier(expr.expression.expression)
3397
3695
  ? `${expr.expression.expression.text}.${expr.expression.name.text}`
3398
3696
  : undefined;
3697
+ const promiseType = inferPromiseCallType(expr);
3698
+ if (promiseType)
3699
+ return promiseType;
3700
+ const objectCallType = inferObjectCallType(expr);
3701
+ if (objectCallType)
3702
+ return objectCallType;
3703
+ if (isIdentifier(expr.expression) && expr.expression.text === 'structuredClone') {
3704
+ return inferExpressionType(expr.arguments?.[0]);
3705
+ }
3399
3706
  // Promise.all([...]) resolves to a slice over a channel
3400
3707
  if (isPropertyAccessExpression(expr.expression) &&
3401
3708
  isIdentifier(expr.expression.expression) &&
@@ -3441,12 +3748,18 @@ function inferExpressionType(expr) {
3441
3748
  if (builtinType && !declaredFunctions.has(expr.expression.text))
3442
3749
  return builtinType;
3443
3750
  }
3444
- if (isPropertyAccessExpression(expr) && expr.name.text === 'length' && isDynamicValue(expr.expression)) {
3751
+ const regexPropertyType = inferRegexPropertyType(expr);
3752
+ if (regexPropertyType)
3753
+ return regexPropertyType;
3754
+ if (isPropertyAccessExpression(expr) && expr.name.text === 'length' && hasDynamicProperties(expr.expression)) {
3445
3755
  return 'float64';
3446
3756
  }
3447
3757
  if ((isPropertyAccessExpression(expr) || isElementAccessExpression(expr)) && isDynamicValue(expr.expression)) {
3448
3758
  return 'interface{}';
3449
3759
  }
3760
+ if (isPropertyAccessExpression(expr) && !isCallee(expr) && isTypeParameterValue(expr.expression)) {
3761
+ return 'interface{}';
3762
+ }
3450
3763
  if (isCallExpression(expr) &&
3451
3764
  isPropertyAccessExpression(expr.expression) &&
3452
3765
  isIdentifier(expr.expression.expression) &&
@@ -3456,6 +3769,15 @@ function inferExpressionType(expr) {
3456
3769
  }
3457
3770
  if (isCallExpression(expr) && isObjectKeysOfDynamic(expr))
3458
3771
  return '[]string';
3772
+ // f?.() is undefined when f is
3773
+ if (isCallExpression(expr) && hasQuestionDot(expr)) {
3774
+ const fnType = inferExpressionType(expr.expression) ?? '';
3775
+ const returnType = fnType.startsWith('func(') ? goFuncReturnType(fnType) : undefined;
3776
+ return returnType ? makeNullableType(returnType) : undefined;
3777
+ }
3778
+ const recordCall = isCallExpression(expr) ? getRecordFunctionCall(expr) : undefined;
3779
+ if (recordCall)
3780
+ return goFuncReturnType(extractMapValueType(recordCall.mapType));
3459
3781
  if (isCallExpression(expr) &&
3460
3782
  isPropertyAccessExpression(expr.expression) &&
3461
3783
  isIdentifier(expr.expression.expression) &&
@@ -3570,15 +3892,17 @@ function inferExpressionType(expr) {
3570
3892
  if (methodName === 'getTime' || methodName === 'valueOf' || methodName === 'getMilliseconds' ||
3571
3893
  methodName === 'getSeconds' || methodName === 'getMinutes' || methodName === 'getHours' ||
3572
3894
  methodName === 'getDate' || methodName === 'getDay' || methodName === 'getMonth' ||
3573
- methodName === 'getFullYear')
3895
+ methodName === 'getFullYear' || methodName.startsWith('getUTC'))
3574
3896
  return 'float64';
3575
3897
  if (methodName === 'toISOString' || methodName === 'toJSON' || methodName === 'toString' ||
3576
3898
  methodName === 'toDateString' || methodName === 'toTimeString')
3577
3899
  return 'string';
3578
3900
  }
3579
3901
  if (ownerType && ownerType.startsWith('*TnMap[')) {
3580
- if (methodName === 'has')
3902
+ if (['has', 'isSubsetOf', 'isSupersetOf', 'isDisjointFrom'].includes(methodName))
3581
3903
  return 'bool';
3904
+ if (['union', 'intersection', 'difference', 'symmetricDifference'].includes(methodName))
3905
+ return ownerType;
3582
3906
  if (methodName === 'get') {
3583
3907
  const valueType = extractMapValueType(ownerType);
3584
3908
  return isStructGoType(valueType) ? `*${valueType}` : valueType;
@@ -3602,6 +3926,10 @@ function inferExpressionType(expr) {
3602
3926
  return 'float64';
3603
3927
  if (['pop', 'shift', 'at'].includes(methodName))
3604
3928
  return elementType;
3929
+ if (['concat', 'toSorted', 'toReversed', 'with', 'copyWithin', 'toSpliced'].includes(methodName))
3930
+ return ownerType;
3931
+ if (methodName === 'reduceRight' && expr.arguments?.[1])
3932
+ return inferExpressionType(expr.arguments[1]);
3605
3933
  if (methodName === 'flat' && elementType.startsWith('[]'))
3606
3934
  return elementType;
3607
3935
  if (methodName === 'flat' || methodName === 'flatMap')
@@ -3698,6 +4026,11 @@ function inferExpressionType(expr) {
3698
4026
  return undefined;
3699
4027
  }
3700
4028
  const BUILTIN_CALL_TYPES = {
4029
+ isFinite: 'bool',
4030
+ encodeURIComponent: 'string',
4031
+ decodeURIComponent: 'string',
4032
+ 'Object.fromEntries': 'interface{}',
4033
+ 'Date.UTC': 'float64',
3701
4034
  'Number.isNaN': 'bool',
3702
4035
  'Number.isFinite': 'bool',
3703
4036
  'Number.isInteger': 'bool',
@@ -3705,6 +4038,156 @@ const BUILTIN_CALL_TYPES = {
3705
4038
  'Object.assign': '*TnMap[string, interface{}]',
3706
4039
  'Date.now': 'float64'
3707
4040
  };
4041
+ // Object.freeze & co. record the integrity level (1 preventExtensions, 2 seal,
4042
+ // 3 freeze) by identity; TypeScript's Readonly<T> result types reject writes
4043
+ const OBJECT_INTEGRITY_LEVELS = { preventExtensions: 1, seal: 2, freeze: 3 };
4044
+ const OBJECT_INTEGRITY_CHECKS = { isExtensible: 1, isSealed: 2, isFrozen: 3 };
4045
+ function getObjectCallName(node) {
4046
+ if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression))
4047
+ return undefined;
4048
+ if (!isIdentifier(node.expression.expression) || node.expression.expression.text !== 'Object')
4049
+ return undefined;
4050
+ return node.expression.name.text;
4051
+ }
4052
+ // The Go type of a property of an object type (anonymous struct, interface or class)
4053
+ function getObjectFieldGoType(objectType, field) {
4054
+ const base = objectType.replace(/^\*/, '').replace(/\[.*$/, '');
4055
+ return (getStructFieldGoType(objectType, field) ??
4056
+ interfacePropertyTypes.get(base)?.get(field) ??
4057
+ classPropertyTypes.get(base)?.get(field));
4058
+ }
4059
+ function inferObjectCallType(expr) {
4060
+ const name = getObjectCallName(expr);
4061
+ if (!name)
4062
+ return undefined;
4063
+ if (OBJECT_INTEGRITY_LEVELS[name] || name === 'defineProperty')
4064
+ return inferExpressionType(expr.arguments?.[0]);
4065
+ if (OBJECT_INTEGRITY_CHECKS[name] || name === 'hasOwn')
4066
+ return 'bool';
4067
+ if (name === 'getOwnPropertyNames')
4068
+ return inferExpressionType({ ...expr, expression: { ...expr.expression, name: { kind: 'Identifier', text: 'keys' } } });
4069
+ return undefined;
4070
+ }
4071
+ function visitObjectCall(node) {
4072
+ const name = getObjectCallName(node);
4073
+ if (!name)
4074
+ return undefined;
4075
+ const args = node.arguments ?? [];
4076
+ if (OBJECT_INTEGRITY_LEVELS[name]) {
4077
+ useHelper('objectIntegrity');
4078
+ return `TnSetIntegrity(${visit(args[0])}, ${OBJECT_INTEGRITY_LEVELS[name]})`;
4079
+ }
4080
+ if (OBJECT_INTEGRITY_CHECKS[name]) {
4081
+ useHelper('objectIntegrity');
4082
+ const check = `TnHasIntegrity(${visit(args[0])}, ${OBJECT_INTEGRITY_CHECKS[name]})`;
4083
+ return name === 'isExtensible' ? `!${check}` : check;
4084
+ }
4085
+ if (name === 'getOwnPropertyNames') {
4086
+ return visit({ ...node, expression: { ...node.expression, name: { kind: 'Identifier', text: 'keys' } } });
4087
+ }
4088
+ const objectType = inferExpressionType(args[0]) ?? '';
4089
+ const key = args[1];
4090
+ if (name === 'hasOwn') {
4091
+ if (isDynamicValue(args[0])) {
4092
+ useHelper('dynamic');
4093
+ return `TnHas(${visit(args[0])}, ${visit(key)})`;
4094
+ }
4095
+ if (objectType.startsWith('*TnMap['))
4096
+ return `${visit(args[0])}.Has(${visit(key)})`;
4097
+ if (!isStringLiteral(key))
4098
+ throw new Error('Object.hasOwn on a typed object needs a literal key');
4099
+ const fieldType = getObjectFieldGoType(objectType, key.text);
4100
+ if (!fieldType)
4101
+ return 'false';
4102
+ // an absent optional primitive is nil
4103
+ return /^\*(float64|string|bool)$/.test(fieldType) ? `(${visit(args[0])}.${goFieldName(key.text)} != nil)` : 'true';
4104
+ }
4105
+ if (name === 'defineProperty') {
4106
+ const descriptor = args[2];
4107
+ const valueProp = isObjectLiteralExpression(descriptor)
4108
+ ? (descriptor.properties ?? []).find((p) => isPropertyAssignment(p) && isIdentifier(p.name) && p.name.text === 'value')
4109
+ : undefined;
4110
+ if (!valueProp)
4111
+ throw new Error('Object.defineProperty needs a { value } descriptor literal');
4112
+ const target = visit(args[0]);
4113
+ if (isDynamicValue(args[0])) {
4114
+ useHelper('dynamic');
4115
+ return `TnSet(${target}, ${visit(key)}, ${visit(valueProp.initializer)})`;
4116
+ }
4117
+ if (objectType.startsWith('*TnMap[')) {
4118
+ return `${target}.Set(${visit(key)}, ${toGoValueOfType(valueProp.initializer, extractMapValueType(objectType))})`;
4119
+ }
4120
+ if (!isStringLiteral(key))
4121
+ throw new Error('Object.defineProperty on a typed object needs a literal key');
4122
+ return `${target}.${goFieldName(key.text)} = ${toGoValueOfType(valueProp.initializer, getObjectFieldGoType(objectType, key.text))}`;
4123
+ }
4124
+ return undefined;
4125
+ }
4126
+ const PROMISE_COMBINATORS = new Set(['race', 'allSettled', 'any']);
4127
+ function isPromiseStaticCall(node, names) {
4128
+ return (isCallExpression(node) &&
4129
+ isPropertyAccessExpression(node.expression) &&
4130
+ isIdentifier(node.expression.expression) &&
4131
+ node.expression.expression.text === 'Promise' &&
4132
+ names.has(node.expression.name.text));
4133
+ }
4134
+ // p.catch(cb) / p.finally(cb) on a promise channel
4135
+ function isPromiseMethodCall(node) {
4136
+ return (isCallExpression(node) &&
4137
+ isPropertyAccessExpression(node.expression) &&
4138
+ ['catch', 'finally'].includes(node.expression.name.text) &&
4139
+ !!inferExpressionType(node.expression.expression)?.startsWith('chan '));
4140
+ }
4141
+ // The value type of Promise.reject(…): from sibling promises in an array, else the
4142
+ // enclosing function's Promise<T>
4143
+ function getRejectedPromiseType(node) {
4144
+ const parent = node.parent;
4145
+ if (isArrayLiteralExpression(parent)) {
4146
+ for (const sibling of parent.elements ?? []) {
4147
+ if (sibling === node || isPromiseStaticCall(sibling, new Set(['reject'])))
4148
+ continue;
4149
+ const type = inferExpressionType(sibling);
4150
+ if (type?.startsWith('chan '))
4151
+ return type.slice(5);
4152
+ }
4153
+ }
4154
+ return getPromiseChannelType(node);
4155
+ }
4156
+ function inferPromiseCallType(expr) {
4157
+ if (isPromiseStaticCall(expr, new Set(['reject'])))
4158
+ return `chan ${getRejectedPromiseType(expr)}`;
4159
+ if (isPromiseStaticCall(expr, PROMISE_COMBINATORS)) {
4160
+ const elem = promiseAllElementType(expr) ?? 'interface{}';
4161
+ return expr.expression.name.text === 'allSettled' ? `chan []*TnSettled[${elem}]` : `chan ${elem}`;
4162
+ }
4163
+ if (isPromiseMethodCall(expr))
4164
+ return inferExpressionType(expr.expression.expression);
4165
+ return undefined;
4166
+ }
4167
+ function visitPromiseCall(node) {
4168
+ const args = node.arguments ?? [];
4169
+ if (isPromiseStaticCall(node, new Set(['reject']))) {
4170
+ useHelper('promise');
4171
+ return `TnPromiseReject[${getRejectedPromiseType(node)}](${args[0] ? visit(args[0]) : 'nil'})`;
4172
+ }
4173
+ if (isPromiseStaticCall(node, PROMISE_COMBINATORS)) {
4174
+ useHelper('promise');
4175
+ const name = node.expression.name.text;
4176
+ return `TnPromise${name[0].toUpperCase()}${name.slice(1)}(${args[0] ? visit(args[0]) : '[]chan interface{}{}'})`;
4177
+ }
4178
+ if (!isPromiseMethodCall(node))
4179
+ return undefined;
4180
+ useHelper('promise');
4181
+ const receiver = visit(node.expression.expression);
4182
+ const callback = args[0];
4183
+ const cb = callback ? visit(callback) : 'func() {}';
4184
+ if (node.expression.name.text === 'finally')
4185
+ return `TnPromiseFinally(${receiver}, ${cb})`;
4186
+ // .catch(() => …) ignores the reason; .catch((e) => value) settles with the value
4187
+ const handler = callback && (callback.parameters ?? []).length === 0 ? `func(_ interface{}) { (${cb})() }` : cb;
4188
+ const returnsValue = callback && !/\)$/.test(inferExpressionType(callback) ?? ')');
4189
+ return `TnPromiseCatch${returnsValue ? 'Value' : ''}(${receiver}, ${handler})`;
4190
+ }
3708
4191
  // Element type of `await Promise.all([...])` from the first promise channel
3709
4192
  function promiseAllElementType(expr) {
3710
4193
  const arrayNode = (expr.arguments ?? [])[0];
@@ -3716,7 +4199,9 @@ const BUILTIN_FUNCTION_TYPES = {
3716
4199
  parseFloat: 'float64',
3717
4200
  parseInt: 'float64',
3718
4201
  Number: 'float64',
3719
- String: 'string'
4202
+ String: 'string',
4203
+ setTimeout: '*time.Timer',
4204
+ setInterval: '*TnInterval'
3720
4205
  };
3721
4206
  const STRING_METHOD_RETURN_TYPES = {
3722
4207
  trim: 'string',
@@ -3886,6 +4371,43 @@ function isObjectTypeName(name) {
3886
4371
  }
3887
4372
  return interfacePropertyTypes.has(name);
3888
4373
  }
4374
+ // The keys a mapped type iterates: string literals of a union, or keyof an object type
4375
+ function getMappedTypeKeys(constraint) {
4376
+ const keys = resolveTypeNode(constraint);
4377
+ if (!keys)
4378
+ return undefined;
4379
+ if (keys.kind === 'TypeOperator' && keys.operator === 'KeyOfKeyword') {
4380
+ const fields = getObjectTypeFields(keys.type);
4381
+ return fields.length > 0 ? fields.map((f) => f.split(' ')[0]) : undefined;
4382
+ }
4383
+ const types = isUnionTypeNode(keys) ? (keys.types ?? []) : [keys];
4384
+ if (!types.every((t) => isLiteralTypeNode(t) && isStringLiteral(t.literal)))
4385
+ return undefined;
4386
+ return types.map((t) => goFieldName(t.literal.text));
4387
+ }
4388
+ function getMappedType(typeNode) {
4389
+ const valueType = typeNode.type ? getOptionalNodeType(typeNode.type, !!typeNode.questionToken) : 'interface{}';
4390
+ const keys = getMappedTypeKeys(typeNode.typeParameter?.constraint);
4391
+ if (keys)
4392
+ return `*struct{ ${keys.map((k) => `${k} ${valueType}`).join('; ')} }`;
4393
+ return 'interface{}';
4394
+ }
4395
+ const UTILITY_TYPES = new Set(['Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'NonNullable']);
4396
+ // "name T" field declarations of an object type (type literal, alias or interface)
4397
+ function getObjectTypeFields(typeNode) {
4398
+ let members = [];
4399
+ const resolved = typeNode.kind === 'TypeLiteral' ? typeNode : undefined;
4400
+ if (resolved)
4401
+ members = resolved.members ?? [];
4402
+ else if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
4403
+ const name = typeNode.typeName.text;
4404
+ const alias = declaredTypeAliases.get(name) ?? typeAliases.get(name);
4405
+ members = alias?.kind === 'TypeLiteral' ? (alias.members ?? []) : (declaredInterfaces.get(name)?.members ?? []);
4406
+ }
4407
+ return members
4408
+ .filter((m) => isPropertySignature(m) && isIdentifier(m.name))
4409
+ .map((m) => `${goFieldName(m.name.text)} ${getOptionalNodeType(m.type, !!m.questionToken)}`);
4410
+ }
3889
4411
  function isStructGoType(goType) {
3890
4412
  return goType.startsWith('struct{') || interfacePropertyTypes.has(goType);
3891
4413
  }
@@ -4071,6 +4593,18 @@ function withNarrowingType(names, infer) {
4071
4593
  return type;
4072
4594
  }
4073
4595
  function visitOptionalCall(node) {
4596
+ // f?.(): call only when the function value is not nil
4597
+ if (hasQuestionDot(node)) {
4598
+ const fnType = inferExpressionType(node.expression) ?? '';
4599
+ const returnType = fnType.startsWith('func(') ? goFuncReturnType(fnType) : undefined;
4600
+ const fn = getTempName('fn');
4601
+ const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
4602
+ if (!returnType)
4603
+ return `func() { if ${fn} := ${visit(node.expression)}; ${fn} != nil { ${fn}(${args}) } }()`;
4604
+ const resultType = makeNullableType(returnType);
4605
+ const value = resultType === returnType ? `${fn}(${args})` : `func() ${resultType} { v := ${fn}(${args}); return &v }()`;
4606
+ return `func() ${resultType} { ${fn} := ${visit(node.expression)}; if ${fn} == nil { return nil }; return ${value} }()`;
4607
+ }
4074
4608
  if (!isPropertyAccessExpression(node.expression)) {
4075
4609
  return `${visit(node.expression)}(${(node.arguments ?? []).map((a) => visit(a)).join(', ')})`;
4076
4610
  }
@@ -4189,7 +4723,7 @@ function visitArrayHigherOrderCall(node) {
4189
4723
  if (!isPropertyAccessExpression(node.expression))
4190
4724
  return undefined;
4191
4725
  const methodName = node.expression.name.text;
4192
- if (!['map', 'filter', 'some', 'find', 'findIndex', 'findLast', 'findLastIndex', 'every', 'forEach', 'reduce', 'join', 'entries', 'flatMap'].includes(methodName)) {
4726
+ if (!['map', 'filter', 'some', 'find', 'findIndex', 'findLast', 'findLastIndex', 'every', 'forEach', 'reduce', 'reduceRight', 'join', 'entries', 'flatMap'].includes(methodName)) {
4193
4727
  return undefined;
4194
4728
  }
4195
4729
  const arrayExprNode = node.expression.expression;
@@ -4294,6 +4828,17 @@ function visitArrayHigherOrderCall(node) {
4294
4828
  const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
4295
4829
  return `func() { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${callbackCall} } }()`;
4296
4830
  }
4831
+ if (methodName === 'reduceRight') {
4832
+ importedPackages.add('slices');
4833
+ const reversed = { kind: 'Identifier', text: `func() []${elementType} { __s := slices.Clone(${arrayExpr}); slices.Reverse(__s); return __s }()` };
4834
+ variableGoTypes.set(reversed.text, ownerType ?? '[]interface{}');
4835
+ const access = { ...node.expression, name: { kind: 'Identifier', text: 'reduce' }, expression: reversed };
4836
+ const call = { ...node, expression: access };
4837
+ reversed.parent = access;
4838
+ access.parent = call;
4839
+ access.name.parent = access;
4840
+ return visitArrayHigherOrderCall(call);
4841
+ }
4297
4842
  if (methodName === 'reduce') {
4298
4843
  const reduceCallback = (node.arguments ?? [])[0];
4299
4844
  if (!reduceCallback)
@@ -4444,6 +4989,24 @@ function getType(typeNode, getArrayType = false) {
4444
4989
  const elementType = getType(typeNode.elementType);
4445
4990
  return getArrayType ? elementType : `[]${elementType}`;
4446
4991
  }
4992
+ if (typeNode.kind === 'MappedType')
4993
+ return getMappedType(typeNode);
4994
+ // readonly T[] → T[]; keyof T → string
4995
+ if (typeNode.kind === 'TypeOperator') {
4996
+ return typeNode.operator === 'KeyOfKeyword' ? 'string' : getType(typeNode.type, getArrayType);
4997
+ }
4998
+ // typeof x → x's type
4999
+ if (typeNode.kind === 'TypeQuery' && isIdentifier(typeNode.exprName)) {
5000
+ return variableGoTypes.get(typeNode.exprName.text) ?? 'interface{}';
5001
+ }
5002
+ // asserts x is T → no value; x is T → bool
5003
+ if (typeNode.kind === 'TypePredicate')
5004
+ return typeNode.assertsModifier ? '' : 'bool';
5005
+ // A & B of object types → one struct with all their fields
5006
+ if (typeNode.kind === 'IntersectionType') {
5007
+ const fields = (typeNode.types ?? []).flatMap((t) => getObjectTypeFields(t));
5008
+ return fields.length > 0 ? `*struct{ ${fields.join('; ')} }` : getType(typeNode.types[0]);
5009
+ }
4447
5010
  // [A, B] tuples are slices: []A when all elements share a type, else []interface{}
4448
5011
  if (typeNode.kind === 'TupleType') {
4449
5012
  const elementType = getTupleElementType(typeNode);
@@ -4518,6 +5081,10 @@ function getType(typeNode, getArrayType = false) {
4518
5081
  if (name === 'Set' && typeNode.typeArguments && typeNode.typeArguments.length === 1) {
4519
5082
  return mapGoType(getType(typeNode.typeArguments[0]), 'struct{}');
4520
5083
  }
5084
+ // Partial / Required / Readonly / Pick / Omit / NonNullable<T, ...> → T's Go type
5085
+ if (UTILITY_TYPES.has(name) && typeNode.typeArguments?.length) {
5086
+ return getType(typeNode.typeArguments[0], getArrayType);
5087
+ }
4521
5088
  const typeArgs = getTypeArguments(typeNode.typeArguments);
4522
5089
  if (classNames.has(name) || isObjectTypeName(name)) {
4523
5090
  return `*${name}${typeArgs}`;
@@ -4550,7 +5117,10 @@ function getType(typeNode, getArrayType = false) {
4550
5117
  case 'any':
4551
5118
  return 'interface{}';
4552
5119
  case 'void':
5120
+ case 'never':
4553
5121
  return '';
5122
+ case 'object':
5123
+ return 'interface{}';
4554
5124
  default:
4555
5125
  return typeName;
4556
5126
  }
@@ -4661,6 +5231,22 @@ function getAcessString(leftSide, rightSide, objectType) {
4661
5231
  return 'process.env';
4662
5232
  }
4663
5233
  }
5234
+ if (leftSide === 'Math') {
5235
+ const mathConstants = {
5236
+ PI: 'math.Pi',
5237
+ E: 'math.E',
5238
+ LN2: 'math.Ln2',
5239
+ LN10: 'math.Ln10',
5240
+ LOG2E: 'math.Log2E',
5241
+ LOG10E: 'math.Log10E',
5242
+ SQRT2: 'math.Sqrt2',
5243
+ SQRT1_2: '(1 / math.Sqrt2)'
5244
+ };
5245
+ if (mathConstants[rightSide]) {
5246
+ importedPackages.add('math');
5247
+ return `float64(${mathConstants[rightSide]})`;
5248
+ }
5249
+ }
4664
5250
  if (leftSide === 'Number') {
4665
5251
  const constants = {
4666
5252
  MAX_SAFE_INTEGER: 'float64(9007199254740991)',
@@ -4701,6 +5287,63 @@ const callHandlers = {
4701
5287
  importedPackages.add('fmt');
4702
5288
  return `fmt.Println("Elapsed time:", time.Since(${getTimerName(args[0])}))`;
4703
5289
  },
5290
+ 'Math.hypot': (_caller, args) => mathCall('Hypot', args),
5291
+ 'Math.cbrt': (_caller, args) => mathCall('Cbrt', args),
5292
+ 'Math.exp': (_caller, args) => mathCall('Exp', args),
5293
+ 'Math.expm1': (_caller, args) => mathCall('Expm1', args),
5294
+ 'Math.log1p': (_caller, args) => mathCall('Log1p', args),
5295
+ 'Math.asin': (_caller, args) => mathCall('Asin', args),
5296
+ 'Math.acos': (_caller, args) => mathCall('Acos', args),
5297
+ 'Math.atan': (_caller, args) => mathCall('Atan', args),
5298
+ 'Math.atan2': (_caller, args) => mathCall('Atan2', args),
5299
+ 'Math.sinh': (_caller, args) => mathCall('Sinh', args),
5300
+ 'Math.cosh': (_caller, args) => mathCall('Cosh', args),
5301
+ 'Math.tanh': (_caller, args) => mathCall('Tanh', args),
5302
+ isFinite: (_caller, args) => {
5303
+ importedPackages.add('math');
5304
+ return `(!math.IsInf(${args[0]}, 0) && !math.IsNaN(${args[0]}))`;
5305
+ },
5306
+ encodeURIComponent: (_caller, args) => {
5307
+ useHelper('uri');
5308
+ return `TnEncodeURIComponent(${args[0]})`;
5309
+ },
5310
+ decodeURIComponent: (_caller, args) => {
5311
+ useHelper('uri');
5312
+ return `TnDecodeURIComponent(${args[0]})`;
5313
+ },
5314
+ 'Object.fromEntries': (_caller, args) => {
5315
+ useHelper('dynamic');
5316
+ return `TnFromEntries(${args[0]})`;
5317
+ },
5318
+ 'Date.UTC': (_caller, args) => {
5319
+ importedPackages.add('time');
5320
+ const part = (i, fallback) => (args[i] ? `int(${args[i]})` : fallback);
5321
+ return `float64(time.Date(${part(0, '1970')}, time.Month(${part(1, '0')}+1), ${part(2, '1')}, ${part(3, '0')}, ${part(4, '0')}, ${part(5, '0')}, ${part(6, '0')}*1000000, time.UTC).UnixMilli())`;
5322
+ },
5323
+ 'Promise.resolve': (_caller, args) => {
5324
+ const valueType = currentCallArgTypes[0] ?? 'interface{}';
5325
+ return `func() chan ${valueType} { __ch := make(chan ${valueType}, 1); __ch <- ${args[0] ?? 'nil'}; return __ch }()`;
5326
+ },
5327
+ structuredClone: (_caller, args) => {
5328
+ useHelper('structuredClone');
5329
+ return `TnStructuredClone(${args[0]})`;
5330
+ },
5331
+ setInterval: (_caller, args) => {
5332
+ useHelper('timerWait');
5333
+ return `TnSetInterval(${args[1] ?? '0'}, ${args[0]})`;
5334
+ },
5335
+ clearInterval: (_caller, args) => {
5336
+ useHelper('timerWait');
5337
+ return `TnClearInterval(${args[0]})`;
5338
+ },
5339
+ queueMicrotask: (_caller, args) => {
5340
+ useHelper('timerWait');
5341
+ return `TnQueueMicrotask(${args[0]})`;
5342
+ },
5343
+ clearTimeout: (_caller, args) => {
5344
+ useHelper('timerWait');
5345
+ return `TnClearTimeout(${args[0]})`;
5346
+ },
4704
5347
  'Math.random': () => {
4705
5348
  importedPackages.add('math/rand');
4706
5349
  return 'rand.Float64()';
@@ -4833,10 +5476,7 @@ const callHandlers = {
4833
5476
  importedPackages.add('reflect');
4834
5477
  return `(reflect.ValueOf(${args[0]}).Kind() == reflect.Slice)`;
4835
5478
  },
4836
- 'String': (_caller, args) => {
4837
- importedPackages.add('fmt');
4838
- return `fmt.Sprintf("%v", ${args[0]})`;
4839
- },
5479
+ 'String': (_caller, args) => jsStringOf(args[0] ?? '""', currentCallArgTypes[0]),
4840
5480
  'String.fromCharCode': (_caller, args) => `string(rune(int(${args[0] ?? '0'})))`,
4841
5481
  // Symbol(description) — unique values for record keys
4842
5482
  Symbol: (_caller, args) => {
@@ -4863,9 +5503,7 @@ const callHandlers = {
4863
5503
  importedPackages.add('math');
4864
5504
  return `(${args[0]} == math.Trunc(${args[0]}) && !math.IsInf(${args[0]}, 0))`;
4865
5505
  },
4866
- 'Boolean': (_caller, args) => {
4867
- return `(${args[0]} != nil && ${args[0]} != false && ${args[0]} != 0 && ${args[0]} != "")`;
4868
- },
5506
+ 'Boolean': (_caller, args) => `(${truthinessCheck(args[0] ?? 'false', currentCallArgTypes[0] ?? 'interface{}')})`,
4869
5507
  };
4870
5508
  const stringMethodHandlers = {
4871
5509
  split: (obj, args) => {
@@ -5011,8 +5649,16 @@ const dateMethodHandlers = {
5011
5649
  getDay: (obj) => `float64(int((${obj}).Weekday()))`,
5012
5650
  getMonth: (obj) => `float64(int((${obj}).Month()) - 1)`,
5013
5651
  getFullYear: (obj) => `float64((${obj}).Year())`,
5014
- toISOString: (obj) => `(${obj}).UTC().Format("2006-01-01T15:04:05.000Z07:00")`,
5015
- toJSON: (obj) => `(${obj}).UTC().Format("2006-01-01T15:04:05.000Z07:00")`,
5652
+ toISOString: (obj) => `(${obj}).UTC().Format("2006-01-02T15:04:05.000Z07:00")`,
5653
+ toJSON: (obj) => `(${obj}).UTC().Format("2006-01-02T15:04:05.000Z07:00")`,
5654
+ getUTCMilliseconds: (obj) => `float64((${obj}).UTC().Nanosecond() / 1000000)`,
5655
+ getUTCSeconds: (obj) => `float64((${obj}).UTC().Second())`,
5656
+ getUTCMinutes: (obj) => `float64((${obj}).UTC().Minute())`,
5657
+ getUTCHours: (obj) => `float64((${obj}).UTC().Hour())`,
5658
+ getUTCDate: (obj) => `float64((${obj}).UTC().Day())`,
5659
+ getUTCDay: (obj) => `float64(int((${obj}).UTC().Weekday()))`,
5660
+ getUTCMonth: (obj) => `float64(int((${obj}).UTC().Month()) - 1)`,
5661
+ getUTCFullYear: (obj) => `float64((${obj}).UTC().Year())`,
5016
5662
  toString: (obj) => `(${obj}).UTC().Format("Mon Jan 02 2006 15:04:05 GMT+0000 (UTC)")`,
5017
5663
  toDateString: (obj) => `(${obj}).UTC().Format("Mon Jan 02 2006")`,
5018
5664
  toTimeString: (obj) => `(${obj}).UTC().Format("15:04:05 GMT+0000 (UTC)")`,
@@ -5071,19 +5717,38 @@ const arrayMethodHandlers = {
5071
5717
  return `func() float64 { __x := ${args[0]}; for __i := len(${obj}) - 1; __i >= 0; __i-- { if ${elementEqualsFor(elementType, `${obj}[__i]`, '__x')} { return float64(__i) } }; return float64(-1) }()`;
5072
5718
  },
5073
5719
  // Sorts in place and returns the array; without a comparator, compares as strings (like JS)
5074
- sort: (obj, args) => {
5075
- importedPackages.add('sort');
5076
- const arrayType = currentReceiverGoType?.startsWith('[]') ? currentReceiverGoType : '[]interface{}';
5077
- let less = `(${args[0]})(__s[i], __s[j]) < 0`;
5078
- if (!args[0]) {
5079
- importedPackages.add('fmt');
5080
- less = `fmt.Sprintf("%v", __s[i]) < fmt.Sprintf("%v", __s[j])`;
5081
- }
5082
- return `func() ${arrayType} { __s := ${obj}; sort.SliceStable(__s, func(i, j int) bool { return ${less} }); return __s }()`;
5083
- },
5720
+ sort: (obj, args) => sortArray(obj, args),
5084
5721
  indexOf: (obj, args) => `func() float64 { __x := ${args[0]}; for __i, __v := range ${obj} { if ${elementEquals('__v', '__x')} { return float64(__i) } }; return float64(-1) }()`,
5085
5722
  includes: (obj, args) => `func() bool { __x := ${args[0]}; for _, __v := range ${obj} { if ${elementEquals('__v', '__x')} { return true } }; return false }()`,
5086
- concat: (obj, args) => `append(${obj}, ${args.join(', ')}...)`,
5723
+ // concat(a, [b, c], d): arrays are flattened one level, other values appended
5724
+ concat: (obj, args) => {
5725
+ const parts = args.map((arg, i) => currentCallArgTypes[i]?.startsWith('[]') ? `__r = append(__r, ${arg}...)` : `__r = append(__r, ${arg})`);
5726
+ return `func() []${receiverElementType()} { __r := append([]${receiverElementType()}{}, ${obj}...); ${parts.join('; ')}; return __r }()`;
5727
+ },
5728
+ toSorted: (obj, args) => {
5729
+ importedPackages.add('slices');
5730
+ return sortArray(`slices.Clone(${obj})`, args);
5731
+ },
5732
+ // copyWithin(target, start, end?): copies in place (overlap-safe) and returns the array
5733
+ copyWithin: (obj, args) => {
5734
+ const end = args[2] ? sliceIndex('__s', args[2]) : 'len(__s)';
5735
+ return `func() []${receiverElementType()} { __s := ${obj}; __t := ${sliceIndex('__s', args[0] ?? '0')}; __start := ${sliceIndex('__s', args[1] ?? '0')}; __end := ${end}; if __start < __end { copy(__s[__t:], __s[__start:__end]) }; return __s }()`;
5736
+ },
5737
+ // toSpliced(start, deleteCount?, ...items): a spliced copy
5738
+ toSpliced: (obj, args) => {
5739
+ importedPackages.add('slices');
5740
+ const elementType = receiverElementType();
5741
+ const end = args.length >= 2 ? `min(__start+max(int(${args[1]}), 0), len(__s))` : 'len(__s)';
5742
+ return `func() []${elementType} { __s := ${obj}; __start := ${sliceIndex('__s', args[0] ?? '0')}; return slices.Concat(__s[:__start:__start], []${elementType}{${args.slice(2).join(', ')}}, __s[${end}:]) }()`;
5743
+ },
5744
+ toReversed: (obj) => {
5745
+ importedPackages.add('slices');
5746
+ return `func() []${receiverElementType()} { __s := slices.Clone(${obj}); slices.Reverse(__s); return __s }()`;
5747
+ },
5748
+ with: (obj, args) => {
5749
+ importedPackages.add('slices');
5750
+ return `func() []${receiverElementType()} { __s := slices.Clone(${obj}); __s[${sliceIndex('__s', args[0])}] = ${args[1]}; return __s }()`;
5751
+ },
5087
5752
  // [][]T → []T (one level, like JS's default depth)
5088
5753
  flat: (obj) => {
5089
5754
  const elementType = receiverElementType();
@@ -5148,12 +5813,25 @@ const setMethodHandlers = {
5148
5813
  has: (obj, args) => `${obj}.Has(${args[0]})`,
5149
5814
  delete: (obj, args) => `${obj}.Delete(${args[0]})`,
5150
5815
  clear: (obj) => `${obj}.Clear()`,
5151
- values: (obj) => `${obj}.Keys()`
5816
+ values: (obj) => `${obj}.Keys()`,
5817
+ union: (obj, args) => `${obj}.Union(${args[0]})`,
5818
+ intersection: (obj, args) => `${obj}.Intersection(${args[0]})`,
5819
+ difference: (obj, args) => `${obj}.Difference(${args[0]})`,
5820
+ symmetricDifference: (obj, args) => `${obj}.SymmetricDifference(${args[0]})`,
5821
+ isSubsetOf: (obj, args) => `${obj}.IsSubsetOf(${args[0]})`,
5822
+ isSupersetOf: (obj, args) => `${args[0]}.IsSubsetOf(${obj})`,
5823
+ isDisjointFrom: (obj, args) => `(${obj}.Intersection(${args[0]}).Len() == 0)`
5152
5824
  };
5153
5825
  function getDynamicCallHandler(caller, objectType) {
5154
5826
  if (promiseResolveName && caller === promiseResolveName) {
5155
5827
  return (_caller, args) => `ch <- ${args[0]}`;
5156
5828
  }
5829
+ if (promiseRejectName && caller === promiseRejectName) {
5830
+ return (_caller, args) => {
5831
+ useHelper('promise');
5832
+ return `TnReject(ch, ${args[0] ?? 'nil'})`;
5833
+ };
5834
+ }
5157
5835
  const dotIndex = caller.lastIndexOf('.');
5158
5836
  if (dotIndex !== -1) {
5159
5837
  const methodName = caller.substring(dotIndex + 1);
@@ -5168,6 +5846,12 @@ function getDynamicCallHandler(caller, objectType) {
5168
5846
  return jsStringOf(obj, objectType === 'number' || objectType === 'float64' ? 'float64' : undefined);
5169
5847
  };
5170
5848
  }
5849
+ if (methodName === 'toPrecision') {
5850
+ return (c, args) => {
5851
+ importedPackages.add('strconv');
5852
+ return `strconv.FormatFloat(${c.substring(0, dotIndex)}, 'g', int(${args[0] ?? '-1'}), 64)`;
5853
+ };
5854
+ }
5171
5855
  // number.toFixed(digits)
5172
5856
  if (methodName === 'toFixed') {
5173
5857
  return (c, args) => {
@@ -5289,7 +5973,9 @@ function isObjectKeysOfDynamic(node) {
5289
5973
  isDynamicValue(node.arguments[0]));
5290
5974
  }
5291
5975
  const ARRAY_ONLY_METHODS = new Set([
5292
- 'map', 'filter', 'some', 'every', 'find', 'findIndex', 'forEach', 'reduce', 'join', 'flat', 'sort', 'reverse'
5976
+ 'map', 'filter', 'some', 'every', 'find', 'findIndex', 'findLast', 'findLastIndex', 'forEach', 'reduce',
5977
+ 'reduceRight', 'join', 'flat', 'flatMap', 'sort', 'reverse', 'toSorted', 'toReversed', 'fill', 'splice',
5978
+ 'copyWithin', 'toSpliced'
5293
5979
  ]);
5294
5980
  const STRING_ONLY_METHODS = new Set([
5295
5981
  'startsWith', 'endsWith', 'trim', 'trimStart', 'trimEnd', 'toUpperCase', 'toLowerCase', 'split',
@@ -5341,7 +6027,7 @@ function getSortComparatorElementType(fn) {
5341
6027
  const call = fn.parent;
5342
6028
  if (!isCallExpression(call) || call.arguments?.[0] !== fn)
5343
6029
  return undefined;
5344
- if (!isPropertyAccessExpression(call.expression) || call.expression.name.text !== 'sort') {
6030
+ if (!isPropertyAccessExpression(call.expression) || !['sort', 'toSorted'].includes(call.expression.name.text)) {
5345
6031
  return undefined;
5346
6032
  }
5347
6033
  const arrayType = inferExpressionType(call.expression.expression);
@@ -5441,6 +6127,9 @@ function jsRegexFlagsToGo(flags) {
5441
6127
  // groups to RE2 syntax: (?<name> → (?P<name>
5442
6128
  function translateJsRegexPattern(pattern) {
5443
6129
  return pattern
6130
+ // \u{1F600} / \u00e9 → RE2's \x{…}
6131
+ .replace(/\\u\{([0-9A-Fa-f]+)\}/g, '\\x{$1}')
6132
+ .replace(/\\u([0-9A-Fa-f]{4})/g, '\\x{$1}')
5444
6133
  .replace(/\\/g, '\\\\')
5445
6134
  .replace(/"/g, '\\"')
5446
6135
  .replace(/\(\?<([A-Za-z_][A-Za-z0-9_]*)>/g, '(?P<$1>');
@@ -5449,12 +6138,57 @@ function translateJsRegexPattern(pattern) {
5449
6138
  // Named groups (?<name> are fine (translated to (?P<name>), so strip them
5450
6139
  // before testing for lookarounds (?= (?! (?<= (?<!
5451
6140
  function needsStatefulRegex(pattern, flags) {
5452
- if (flags.includes('g'))
6141
+ // /g and /y keep lastIndex state; /u keeps its flag for .flags
6142
+ if (flags.includes('g') || flags.includes('y') || flags.includes('u'))
5453
6143
  return true;
5454
6144
  const stripped = pattern.replace(/\(\?<[A-Za-z_][A-Za-z0-9_]*>/g, '');
5455
6145
  // (?= (?! and any remaining (?< (lookbehind) need the backtracker
5456
6146
  return /\\[1-9]|\(\?[=!<]/.test(stripped);
5457
6147
  }
6148
+ const REGEX_FLAG_PROPERTIES = {
6149
+ global: 'g', ignoreCase: 'i', multiline: 'm', dotAll: 's', unicode: 'u', sticky: 'y'
6150
+ };
6151
+ function inferRegexPropertyType(expr) {
6152
+ if (!isPropertyAccessExpression(expr))
6153
+ return undefined;
6154
+ const receiverType = inferExpressionType(expr.expression);
6155
+ if (receiverType !== '*TnRegex' && receiverType !== '*regexp.Regexp')
6156
+ return undefined;
6157
+ const name = expr.name.text;
6158
+ if (name === 'lastIndex')
6159
+ return 'float64';
6160
+ if (name === 'source' || name === 'flags')
6161
+ return 'string';
6162
+ if (REGEX_FLAG_PROPERTIES[name])
6163
+ return 'bool';
6164
+ return undefined;
6165
+ }
6166
+ function visitRegexProperty(node) {
6167
+ const type = inferRegexPropertyType(node);
6168
+ if (!type || isCallee(node))
6169
+ return undefined;
6170
+ const name = node.name.text;
6171
+ const receiver = visit(node.expression);
6172
+ useHelper('fancyRegex');
6173
+ if (inferExpressionType(node.expression) === '*TnRegex') {
6174
+ if (name === 'lastIndex' || name === 'source' || name === 'flags')
6175
+ return `${receiver}.${name}`;
6176
+ importedPackages.add('strings');
6177
+ return `strings.Contains(${receiver}.flags, "${REGEX_FLAG_PROPERTIES[name]}")`;
6178
+ }
6179
+ // plain RE2 regexes have no state; their flags (i/m/s) lead the pattern
6180
+ if (name === 'lastIndex')
6181
+ return 'float64(0)';
6182
+ if (name === 'source')
6183
+ return `TnRegexpSource(${receiver})`;
6184
+ if (name === 'flags')
6185
+ return `TnRegexpFlags(${receiver})`;
6186
+ importedPackages.add('strings');
6187
+ return `strings.Contains(TnRegexpFlags(${receiver}), "${REGEX_FLAG_PROPERTIES[name]}")`;
6188
+ }
6189
+ function isStringRawTag(tag) {
6190
+ return isPropertyAccessExpression(tag) && isIdentifier(tag.expression) && tag.expression.text === 'String' && tag.name.text === 'raw';
6191
+ }
5458
6192
  // Whether a visited regex argument is the stateful wrapper type
5459
6193
  function isTnRegexArg(arg) {
5460
6194
  return arg.startsWith('TnRegexCompile(') || variableGoTypes.get(arg) === '*TnRegex';
@@ -5465,7 +6199,8 @@ function getTypeParameters(typeParameters) {
5465
6199
  const params = typeParameters.map((tp) => {
5466
6200
  const name = visit(tp.name);
5467
6201
  const constraint = tp.constraint ? getType(tp.constraint) : 'any';
5468
- return `${name} ${constraint}`;
6202
+ // Go constraints cannot require fields: object-type bounds become any
6203
+ return `${name} ${constraint.startsWith('*') ? 'any' : constraint}`;
5469
6204
  });
5470
6205
  return `[${params.join(', ')}]`;
5471
6206
  }
@@ -5737,7 +6472,8 @@ function getPromiseChannelType(node) {
5737
6472
  while (parent) {
5738
6473
  if (isFunctionDeclaration(parent) ||
5739
6474
  isMethodDeclaration(parent) ||
5740
- isFunctionExpression(parent)) {
6475
+ isFunctionExpression(parent) ||
6476
+ isArrowFunction(parent)) {
5741
6477
  if (parent.type &&
5742
6478
  isTypeReferenceNode(parent.type) &&
5743
6479
  isIdentifier(parent.type.typeName)) {
@@ -5764,6 +6500,9 @@ function emitChannelFunction(node, isAsync) {
5764
6500
  }
5765
6501
  const chanType = isAsync ? getPromiseChannelType(node) : getGeneratorYieldType(node);
5766
6502
  const channelVar = '__ch';
6503
+ if (isAsync)
6504
+ useHelper('promise');
6505
+ const rejectOnPanic = isAsync ? `\n\t\tdefer TnRejectOnPanic(${channelVar})` : '';
5767
6506
  const prevChannel = asyncChannelVar;
5768
6507
  const prevFn = asyncFunctionNode;
5769
6508
  asyncChannelVar = channelVar;
@@ -5771,7 +6510,7 @@ function emitChannelFunction(node, isAsync) {
5771
6510
  const body = visit(node.body, { prefixBlockContent: parameterInfo.prefixBlockContent });
5772
6511
  asyncChannelVar = prevChannel;
5773
6512
  asyncFunctionNode = prevFn;
5774
- return (`func ${name}${typeParams}(${parameterInfo.signature}) chan ${chanType} {\n\t${channelVar} := make(chan ${chanType})\n\tgo func() {\n\t\tdefer close(${channelVar})\n\t\t${body.trim()}\n\t}()\n\treturn ${channelVar}\n}`);
6513
+ return (`func ${name}${typeParams}(${parameterInfo.signature}) chan ${chanType} {\n\t${channelVar} := make(chan ${chanType})\n\tgo func() {\n\t\tdefer close(${channelVar})${rejectOnPanic}\n\t\t${body.trim()}\n\t}()\n\treturn ${channelVar}\n}`);
5775
6514
  }
5776
6515
  // The type a generator yields: from its first yield expression's operand
5777
6516
  function getGeneratorYieldType(fn) {
@@ -5793,10 +6532,14 @@ function visitPromiseReturn(node, options) {
5793
6532
  const channelType = getPromiseChannelType(node);
5794
6533
  const resolveParam = callback.parameters[0];
5795
6534
  const resolveName = resolveParam ? visit(resolveParam.name) : '';
6535
+ const rejectParam = callback.parameters[1];
5796
6536
  const prevResolveName = promiseResolveName;
6537
+ const prevRejectName = promiseRejectName;
5797
6538
  promiseResolveName = resolveName;
6539
+ promiseRejectName = rejectParam ? visit(rejectParam.name) : '';
5798
6540
  const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
5799
6541
  promiseResolveName = prevResolveName;
6542
+ promiseRejectName = prevRejectName;
5800
6543
  return (`ch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch` +
5801
6544
  (options.inline ? '' : ';\n\t'));
5802
6545
  }
@@ -5808,10 +6551,14 @@ function visitNewPromise(node) {
5808
6551
  const channelType = getPromiseChannelType(node);
5809
6552
  const resolveParam = callback.parameters[0];
5810
6553
  const resolveName = resolveParam ? visit(resolveParam.name) : '';
6554
+ const rejectParam = callback.parameters[1];
5811
6555
  const prevResolveName = promiseResolveName;
6556
+ const prevRejectName = promiseRejectName;
5812
6557
  promiseResolveName = resolveName;
6558
+ promiseRejectName = rejectParam ? visit(rejectParam.name) : '';
5813
6559
  const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
5814
6560
  promiseResolveName = prevResolveName;
6561
+ promiseRejectName = prevRejectName;
5815
6562
  return `func() chan ${channelType} {\n\t\tch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch;\n\t}()`;
5816
6563
  }
5817
6564
  function extractMapValueType(mapType) {
@@ -6029,7 +6776,11 @@ function needPkg(pkg) {
6029
6776
  // since the import list is serialized before helpers are emitted.
6030
6777
  const helperRequires = {
6031
6778
  objectAssign: ['orderedMap'],
6032
- promiseAll: ['orderedMap', 'asyncWait'],
6779
+ promiseAll: ['orderedMap', 'asyncWait', 'promise'],
6780
+ promise: ['error'],
6781
+ objectIntegrity: [],
6782
+ structuredClone: [],
6783
+ globalThis: ['dynamic'],
6033
6784
  namedGroups: ['orderedMap'],
6034
6785
  fancyRegex: ['orderedMap'],
6035
6786
  dynamic: ['error'],
@@ -6205,7 +6956,7 @@ const helperPackages = {
6205
6956
  jsonStringify: ['encoding/json', 'bytes', 'strings', 'unsafe'],
6206
6957
  objectAssign: ['reflect', 'unsafe'],
6207
6958
  typeOf: ['reflect'],
6208
- dynamic: ['math', 'strings', 'fmt', 'sort', 'reflect', 'strconv', 'regexp'],
6959
+ dynamic: ['math', 'strings', 'fmt', 'sort', 'reflect', 'strconv', 'regexp', 'unsafe'],
6209
6960
  regexReplaceFunc: ['regexp'],
6210
6961
  readFile: ['os'],
6211
6962
  writeFile: ['os'],
@@ -6225,7 +6976,12 @@ const helperPackages = {
6225
6976
  moduleUrl: ['os'],
6226
6977
  asyncWait: ['sync'],
6227
6978
  timerWait: ['sync', 'time'],
6979
+ uri: ['strings', 'fmt', 'net/url'],
6228
6980
  promiseAll: [],
6981
+ promise: ['sync'],
6982
+ objectIntegrity: ['sync', 'reflect'],
6983
+ structuredClone: ['reflect', 'unsafe'],
6984
+ globalThis: [],
6229
6985
  exec: ['os/exec', 'bytes', 'runtime'],
6230
6986
  execSync: ['os/exec', 'bytes', 'runtime'],
6231
6987
  runInherit: ['os/exec', 'os'],
@@ -6256,6 +7012,18 @@ func TnGet(obj interface{}, key string) interface{} {
6256
7012
  return o.message
6257
7013
  }
6258
7014
  }
7015
+ rv := reflect.ValueOf(obj)
7016
+ if rv.Kind() == reflect.Pointer && !rv.IsNil() && rv.Elem().Kind() == reflect.Struct {
7017
+ for _, name := range []string{key, key + "_"} {
7018
+ if f := rv.Elem().FieldByName(name); f.IsValid() {
7019
+ // Unexported field: read it through a pointer to bypass the flag
7020
+ return reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem().Interface()
7021
+ }
7022
+ }
7023
+ }
7024
+ if key == "length" {
7025
+ return TnLength(obj)
7026
+ }
6259
7027
  return nil
6260
7028
  }
6261
7029
 
@@ -6267,6 +7035,22 @@ func TnSet(obj interface{}, key string, value interface{}) {
6267
7035
  panic("TnSet: cannot set property " + key)
6268
7036
  }
6269
7037
 
7038
+ func TnSetIndex(obj interface{}, key interface{}, value interface{}) {
7039
+ switch o := obj.(type) {
7040
+ case map[string]interface{}:
7041
+ if k, ok := key.(string); ok {
7042
+ o[k] = value
7043
+ return
7044
+ }
7045
+ case []interface{}:
7046
+ if i, ok := key.(float64); ok && i >= 0 && int(i) < len(o) {
7047
+ o[int(i)] = value
7048
+ return
7049
+ }
7050
+ }
7051
+ panic(fmt.Sprintf("TnSetIndex: cannot set property %v", key))
7052
+ }
7053
+
6270
7054
  func TnIndex(obj interface{}, key interface{}) interface{} {
6271
7055
  switch o := obj.(type) {
6272
7056
  case map[string]interface{}:
@@ -6324,6 +7108,9 @@ func TnLength(v interface{}) float64 {
6324
7108
  case []interface{}:
6325
7109
  return float64(len(t))
6326
7110
  }
7111
+ if rv := reflect.ValueOf(v); rv.Kind() == reflect.Slice || rv.Kind() == reflect.Map {
7112
+ return float64(rv.Len())
7113
+ }
6327
7114
  return 0
6328
7115
  }
6329
7116
 
@@ -6466,6 +7253,19 @@ func TnDelete(obj interface{}, key interface{}) bool {
6466
7253
  return true
6467
7254
  }
6468
7255
 
7256
+ // Object.fromEntries: [key, value] pairs → a dynamic object
7257
+ func TnFromEntries(entries interface{}) interface{} {
7258
+ result := map[string]interface{}{}
7259
+ list := reflect.ValueOf(entries)
7260
+ for i := 0; i < list.Len(); i++ {
7261
+ pair := reflect.ValueOf(list.Index(i).Interface())
7262
+ if pair.Kind() == reflect.Slice && pair.Len() >= 2 {
7263
+ result[fmt.Sprint(pair.Index(0).Interface())] = pair.Index(1).Interface()
7264
+ }
7265
+ }
7266
+ return result
7267
+ }
7268
+
6469
7269
  func TnHas(obj interface{}, key interface{}) bool {
6470
7270
  if o, ok := obj.(map[string]interface{}); ok {
6471
7271
  if k, ok := key.(string); ok {
@@ -6673,7 +7473,7 @@ func tnJSONWrite(buf *bytes.Buffer, rv reflect.Value, indent string, depth int)
6673
7473
  case reflect.Float32, reflect.Float64:
6674
7474
  buf.WriteString(TnNumStr(rv.Float()))
6675
7475
  case reflect.String:
6676
- data, _ := json.Marshal(rv.String())
7476
+ data, _ := tnJSONMarshal(rv.String())
6677
7477
  buf.Write(data)
6678
7478
  case reflect.Slice, reflect.Array:
6679
7479
  if rv.Kind() == reflect.Slice && rv.IsNil() {
@@ -6704,7 +7504,7 @@ func tnJSONWrite(buf *bytes.Buffer, rv reflect.Value, indent string, depth int)
6704
7504
  buf.WriteByte(',')
6705
7505
  tnJSONNewline(buf, indent, depth+1)
6706
7506
  }
6707
- kd, _ := json.Marshal(ks)
7507
+ kd, _ := tnJSONMarshal(ks)
6708
7508
  buf.Write(kd)
6709
7509
  buf.WriteByte(':')
6710
7510
  tnJSONWrite(buf, byKey[ks], indent, depth+1)
@@ -6739,20 +7539,31 @@ func tnJSONWrite(buf *bytes.Buffer, rv reflect.Value, indent string, depth int)
6739
7539
  tnJSONNewline(buf, indent, depth+1)
6740
7540
  }
6741
7541
  first = false
6742
- kd, _ := json.Marshal(f.Name)
7542
+ kd, _ := tnJSONMarshal(f.Name)
6743
7543
  buf.Write(kd)
6744
7544
  buf.WriteByte(':')
6745
7545
  tnJSONWrite(buf, value, indent, depth+1)
6746
7546
  }
6747
7547
  buf.WriteByte('}')
6748
7548
  default:
6749
- data, err := json.Marshal(rv.Interface())
7549
+ data, err := tnJSONMarshal(rv.Interface())
6750
7550
  if err != nil {
6751
7551
  buf.WriteString("null")
6752
7552
  return
6753
7553
  }
6754
7554
  buf.Write(data)
6755
7555
  }
7556
+ }
7557
+
7558
+ // json.Marshal without HTML escaping of <, > and & (JSON.stringify does not escape them)
7559
+ func tnJSONMarshal(v interface{}) ([]byte, error) {
7560
+ var buf bytes.Buffer
7561
+ enc := json.NewEncoder(&buf)
7562
+ enc.SetEscapeHTML(false)
7563
+ if err := enc.Encode(v); err != nil {
7564
+ return nil, err
7565
+ }
7566
+ return bytes.TrimSuffix(buf.Bytes(), []byte("\\n")), nil
6756
7567
  }`,
6757
7568
  orderedMap: `// TnMap: JS Map/Set/object semantics — keys iterate in insertion order
6758
7569
  type TnMap[K comparable, V any] struct {
@@ -6811,6 +7622,51 @@ func (m *TnMap[K, V]) Delete(k K) bool {
6811
7622
  return true
6812
7623
  }
6813
7624
 
7625
+ // Set operations (ES2025), in the receiver's insertion order then the argument's
7626
+ func (m *TnMap[K, V]) Union(o *TnMap[K, V]) *TnMap[K, V] {
7627
+ r := TnNewMap[K, V]()
7628
+ for _, k := range m.keys {
7629
+ r.Set(k, m.values[k])
7630
+ }
7631
+ for _, k := range o.keys {
7632
+ r.Set(k, o.values[k])
7633
+ }
7634
+ return r
7635
+ }
7636
+
7637
+ func (m *TnMap[K, V]) Intersection(o *TnMap[K, V]) *TnMap[K, V] {
7638
+ r := TnNewMap[K, V]()
7639
+ for _, k := range m.keys {
7640
+ if o.Has(k) {
7641
+ r.Set(k, m.values[k])
7642
+ }
7643
+ }
7644
+ return r
7645
+ }
7646
+
7647
+ func (m *TnMap[K, V]) Difference(o *TnMap[K, V]) *TnMap[K, V] {
7648
+ r := TnNewMap[K, V]()
7649
+ for _, k := range m.keys {
7650
+ if !o.Has(k) {
7651
+ r.Set(k, m.values[k])
7652
+ }
7653
+ }
7654
+ return r
7655
+ }
7656
+
7657
+ func (m *TnMap[K, V]) SymmetricDifference(o *TnMap[K, V]) *TnMap[K, V] {
7658
+ return m.Difference(o).Union(o.Difference(m))
7659
+ }
7660
+
7661
+ func (m *TnMap[K, V]) IsSubsetOf(o *TnMap[K, V]) bool {
7662
+ for _, k := range m.keys {
7663
+ if !o.Has(k) {
7664
+ return false
7665
+ }
7666
+ }
7667
+ return true
7668
+ }
7669
+
6814
7670
  func (m *TnMap[K, V]) Clear() {
6815
7671
  m.keys = nil
6816
7672
  m.values = map[K]V{}
@@ -6910,15 +7766,44 @@ func TnRegexExecNamed(re *regexp.Regexp, s string) *TnMatch {
6910
7766
  type TnRegex struct {
6911
7767
  re *regexp.Regexp
6912
7768
  bt *tnBt
6913
- lastIndex int
7769
+ lastIndex float64
6914
7770
  global bool
7771
+ sticky bool
7772
+ source string
7773
+ flags string
6915
7774
  }
6916
7775
 
6917
- func TnRegexCompile(pattern string, global bool) *TnRegex {
6918
- if re, err := regexp.Compile(pattern); err == nil {
6919
- return &TnRegex{re: re, global: global}
7776
+ func TnRegexCompile(pattern string, flags string, source string) *TnRegex {
7777
+ r := &TnRegex{global: strings.Contains(flags, "g"), sticky: strings.Contains(flags, "y"), source: source, flags: flags}
7778
+ prefix := ""
7779
+ for _, f := range "ims" {
7780
+ if strings.ContainsRune(flags, f) {
7781
+ prefix += string(f)
7782
+ }
6920
7783
  }
6921
- return &TnRegex{bt: tnBtCompile(pattern), global: global}
7784
+ if prefix != "" {
7785
+ prefix = "(?" + prefix + ")"
7786
+ }
7787
+ if re, err := regexp.Compile(prefix + pattern); err == nil {
7788
+ r.re = re
7789
+ } else {
7790
+ r.bt = tnBtCompile(pattern)
7791
+ }
7792
+ return r
7793
+ }
7794
+
7795
+ // .source / .flags of a plain RE2 regex: flags lead the pattern as (?ims)
7796
+ var tnRegexpFlagPrefix = regexp.MustCompile("^\\\\(\\\\?([ims]+)\\\\)")
7797
+
7798
+ func TnRegexpSource(re *regexp.Regexp) string {
7799
+ return strings.ReplaceAll(tnRegexpFlagPrefix.ReplaceAllString(re.String(), ""), "(?P<", "(?<")
7800
+ }
7801
+
7802
+ func TnRegexpFlags(re *regexp.Regexp) string {
7803
+ if m := tnRegexpFlagPrefix.FindStringSubmatch(re.String()); m != nil {
7804
+ return m[1]
7805
+ }
7806
+ return ""
6922
7807
  }
6923
7808
 
6924
7809
  func (r *TnRegex) Test(s string) bool {
@@ -7065,15 +7950,15 @@ func (r *TnRegex) find(s string) ([]string, []int) {
7065
7950
  runs := []rune(s)
7066
7951
  if r.bt != nil {
7067
7952
  start := 0
7068
- if r.global {
7069
- if r.lastIndex > len(runs) {
7953
+ if r.global || r.sticky {
7954
+ if int(r.lastIndex) > len(runs) {
7070
7955
  r.lastIndex = 0
7071
7956
  return nil, nil
7072
7957
  }
7073
- start = r.lastIndex
7958
+ start = int(r.lastIndex)
7074
7959
  }
7075
7960
  offs := tnByteOffsets(runs)
7076
- for pos := start; pos <= len(runs); pos++ {
7961
+ for pos := start; pos <= len(runs) && (!r.sticky || pos == start); pos++ {
7077
7962
  caps := make([]int, (r.bt.ngroups+1)*2)
7078
7963
  for i := range caps {
7079
7964
  caps[i] = -1
@@ -7093,22 +7978,22 @@ func (r *TnRegex) find(s string) ([]string, []int) {
7093
7978
  loc[2*i], loc[2*i+1] = -1, -1
7094
7979
  }
7095
7980
  }
7096
- if r.global {
7981
+ if r.global || r.sticky {
7097
7982
  next := end
7098
7983
  if next == pos {
7099
7984
  next++
7100
7985
  }
7101
- r.lastIndex = next
7986
+ r.lastIndex = float64(next)
7102
7987
  }
7103
7988
  return groups, loc
7104
7989
  }
7105
7990
  }
7106
- if r.global {
7991
+ if r.global || r.sticky {
7107
7992
  r.lastIndex = 0
7108
7993
  }
7109
7994
  return nil, nil
7110
7995
  }
7111
- if !r.global {
7996
+ if !r.global && !r.sticky {
7112
7997
  loc := r.re.FindStringSubmatchIndex(s)
7113
7998
  if loc == nil {
7114
7999
  return nil, nil
@@ -7121,16 +8006,16 @@ func (r *TnRegex) find(s string) ([]string, []int) {
7121
8006
  }
7122
8007
  return groups, loc
7123
8008
  }
7124
- if r.lastIndex >= len(runs) {
8009
+ if int(r.lastIndex) >= len(runs) {
7125
8010
  r.lastIndex = 0
7126
8011
  return nil, nil
7127
8012
  }
7128
8013
  byteStart := 0
7129
- for i := 0; i < r.lastIndex; i++ {
8014
+ for i := 0; i < int(r.lastIndex); i++ {
7130
8015
  byteStart += len(string(runs[i]))
7131
8016
  }
7132
8017
  loc := r.re.FindStringSubmatchIndex(s[byteStart:])
7133
- if loc == nil {
8018
+ if loc == nil || (r.sticky && loc[0] != 0) {
7134
8019
  r.lastIndex = 0
7135
8020
  return nil, nil
7136
8021
  }
@@ -7145,16 +8030,16 @@ func (r *TnRegex) find(s string) ([]string, []int) {
7145
8030
  groups[i] = s[loc[2*i]:loc[2*i+1]]
7146
8031
  }
7147
8032
  }
7148
- endRune := r.lastIndex
8033
+ endRune := int(r.lastIndex)
7149
8034
  bo := byteStart
7150
- for ri := r.lastIndex; ri < len(runs) && bo < loc[1]; ri++ {
8035
+ for ri := int(r.lastIndex); ri < len(runs) && bo < loc[1]; ri++ {
7151
8036
  bo += len(string(runs[ri]))
7152
8037
  endRune = ri + 1
7153
8038
  }
7154
- if endRune == r.lastIndex {
8039
+ if endRune == int(r.lastIndex) {
7155
8040
  endRune++
7156
8041
  }
7157
- r.lastIndex = endRune
8042
+ r.lastIndex = float64(endRune)
7158
8043
  return groups, loc
7159
8044
  }
7160
8045
 
@@ -7926,14 +8811,358 @@ func TnNewSymbol(description string) *TnSymbol {
7926
8811
  return &TnSymbol{description: description}
7927
8812
  }`,
7928
8813
  timerWait: `// Pending setTimeout timers are waited on before the process exits
7929
- var __tnTimers sync.WaitGroup`,
8814
+ var __tnTimers sync.WaitGroup
8815
+
8816
+ func TnSetTimeout(ms float64, fn func()) *time.Timer {
8817
+ __tnTimers.Add(1)
8818
+ return time.AfterFunc(time.Duration(ms*float64(time.Millisecond)), func() {
8819
+ defer __tnTimers.Done()
8820
+ fn()
8821
+ TnRunMicrotasks()
8822
+ })
8823
+ }
8824
+
8825
+ // setInterval: fn every ms until cleared (the process lives until then)
8826
+ type TnInterval struct {
8827
+ stop chan struct{}
8828
+ once sync.Once
8829
+ }
8830
+
8831
+ func TnSetInterval(ms float64, fn func()) *TnInterval {
8832
+ __tnTimers.Add(1)
8833
+ t := &TnInterval{stop: make(chan struct{})}
8834
+ go func() {
8835
+ ticker := time.NewTicker(max(time.Duration(ms*float64(time.Millisecond)), time.Millisecond))
8836
+ defer ticker.Stop()
8837
+ for {
8838
+ select {
8839
+ case <-t.stop:
8840
+ return
8841
+ case <-ticker.C:
8842
+ fn()
8843
+ TnRunMicrotasks()
8844
+ }
8845
+ }
8846
+ }()
8847
+ return t
8848
+ }
8849
+
8850
+ func TnClearInterval(t *TnInterval) {
8851
+ if t != nil {
8852
+ t.once.Do(func() {
8853
+ close(t.stop)
8854
+ __tnTimers.Done()
8855
+ })
8856
+ }
8857
+ }
8858
+
8859
+ // queueMicrotask: run after the current synchronous code (end of main or of a timer callback)
8860
+ var tnMicrotasks []func()
8861
+ var tnMicrotasksMu sync.Mutex
8862
+
8863
+ func TnQueueMicrotask(fn func()) {
8864
+ tnMicrotasksMu.Lock()
8865
+ tnMicrotasks = append(tnMicrotasks, fn)
8866
+ tnMicrotasksMu.Unlock()
8867
+ }
8868
+
8869
+ func TnRunMicrotasks() {
8870
+ for {
8871
+ tnMicrotasksMu.Lock()
8872
+ if len(tnMicrotasks) == 0 {
8873
+ tnMicrotasksMu.Unlock()
8874
+ return
8875
+ }
8876
+ fn := tnMicrotasks[0]
8877
+ tnMicrotasks = tnMicrotasks[1:]
8878
+ tnMicrotasksMu.Unlock()
8879
+ fn()
8880
+ }
8881
+ }
8882
+
8883
+ func TnClearTimeout(t *time.Timer) {
8884
+ if t != nil && t.Stop() {
8885
+ __tnTimers.Done()
8886
+ }
8887
+ }`,
8888
+ uri: `// encodeURIComponent: escapes everything except A-Z a-z 0-9 - _ . ! ~ * ' ( )
8889
+ func TnEncodeURIComponent(s string) string {
8890
+ var b strings.Builder
8891
+ for _, c := range []byte(s) {
8892
+ if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || strings.IndexByte("-_.!~*'()", c) >= 0 {
8893
+ b.WriteByte(c)
8894
+ } else {
8895
+ fmt.Fprintf(&b, "%%%02X", c)
8896
+ }
8897
+ }
8898
+ return b.String()
8899
+ }
8900
+
8901
+ func TnDecodeURIComponent(s string) string {
8902
+ decoded, err := tnurl.PathUnescape(s)
8903
+ if err != nil {
8904
+ panic(err)
8905
+ }
8906
+ return decoded
8907
+ }`,
8908
+ objectIntegrity: `// Object.freeze / seal / preventExtensions levels, by object identity
8909
+ var tnIntegrity sync.Map
8910
+
8911
+ func tnIdentity(v interface{}) (uintptr, bool) {
8912
+ rv := reflect.ValueOf(v)
8913
+ switch rv.Kind() {
8914
+ case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Func, reflect.Chan:
8915
+ return rv.Pointer(), true
8916
+ }
8917
+ return 0, false
8918
+ }
8919
+
8920
+ func TnSetIntegrity[T any](v T, level int) T {
8921
+ if id, ok := tnIdentity(v); ok {
8922
+ if prev, had := tnIntegrity.Load(id); !had || prev.(int) < level {
8923
+ tnIntegrity.Store(id, level)
8924
+ }
8925
+ }
8926
+ return v
8927
+ }
8928
+
8929
+ // Primitives are always frozen
8930
+ func TnHasIntegrity(v interface{}, level int) bool {
8931
+ id, ok := tnIdentity(v)
8932
+ if !ok {
8933
+ return true
8934
+ }
8935
+ l, had := tnIntegrity.Load(id)
8936
+ return had && l.(int) >= level
8937
+ }`,
8938
+ structuredClone: `// structuredClone: a deep copy (pointers, structs, slices, maps)
8939
+ func TnStructuredClone[T any](v T) T {
8940
+ return tnCloneValue(reflect.ValueOf(&v).Elem()).Interface().(T)
8941
+ }
8942
+
8943
+ func tnCloneValue(v reflect.Value) reflect.Value {
8944
+ switch v.Kind() {
8945
+ case reflect.Pointer:
8946
+ if v.IsNil() {
8947
+ return v
8948
+ }
8949
+ c := reflect.New(v.Elem().Type())
8950
+ c.Elem().Set(tnCloneValue(v.Elem()))
8951
+ return c
8952
+ case reflect.Struct:
8953
+ if !v.CanAddr() {
8954
+ tmp := reflect.New(v.Type()).Elem()
8955
+ tmp.Set(v)
8956
+ v = tmp
8957
+ }
8958
+ c := reflect.New(v.Type()).Elem()
8959
+ for i := 0; i < v.NumField(); i++ {
8960
+ // unexported fields are read and written through their addresses
8961
+ src := reflect.NewAt(v.Field(i).Type(), unsafe.Pointer(v.Field(i).UnsafeAddr())).Elem()
8962
+ dst := reflect.NewAt(c.Field(i).Type(), unsafe.Pointer(c.Field(i).UnsafeAddr())).Elem()
8963
+ dst.Set(tnCloneValue(src))
8964
+ }
8965
+ return c
8966
+ case reflect.Slice:
8967
+ if v.IsNil() {
8968
+ return v
8969
+ }
8970
+ c := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
8971
+ for i := 0; i < v.Len(); i++ {
8972
+ c.Index(i).Set(tnCloneValue(v.Index(i)))
8973
+ }
8974
+ return c
8975
+ case reflect.Map:
8976
+ if v.IsNil() {
8977
+ return v
8978
+ }
8979
+ c := reflect.MakeMapWithSize(v.Type(), v.Len())
8980
+ iter := v.MapRange()
8981
+ for iter.Next() {
8982
+ c.SetMapIndex(iter.Key(), tnCloneValue(iter.Value()))
8983
+ }
8984
+ return c
8985
+ case reflect.Interface:
8986
+ if v.IsNil() {
8987
+ return v
8988
+ }
8989
+ c := reflect.New(v.Type()).Elem()
8990
+ c.Set(tnCloneValue(v.Elem()))
8991
+ return c
8992
+ }
8993
+ return v
8994
+ }`,
8995
+ globalThis: `// (globalThis as any).x: properties set on the global object at runtime
8996
+ var TnGlobalThis = map[string]interface{}{}`,
8997
+ promise: `// Promises are channels; a rejected promise is a closed channel with a reason
8998
+ var tnRejections sync.Map
8999
+
9000
+ func TnReject[T any](ch chan T, reason interface{}) {
9001
+ tnRejections.Store(ch, reason)
9002
+ close(ch)
9003
+ }
9004
+
9005
+ // Deferred in async functions: a throw rejects the function's promise
9006
+ func TnRejectOnPanic[T any](ch chan T) {
9007
+ if r := recover(); r != nil {
9008
+ tnRejections.Store(ch, r)
9009
+ }
9010
+ }
9011
+
9012
+ // Waits for a promise: its value, or its rejection reason
9013
+ func tnSettle[T any](ch chan T) (T, interface{}, bool) {
9014
+ v, ok := <-ch
9015
+ if !ok {
9016
+ if reason, rejected := tnRejections.Load(ch); rejected {
9017
+ return v, reason, true
9018
+ }
9019
+ }
9020
+ return v, nil, false
9021
+ }
9022
+
9023
+ // await: the value, or the rejection rethrown
9024
+ func TnAwait[T any](ch chan T) T {
9025
+ v, reason, rejected := tnSettle(ch)
9026
+ if rejected {
9027
+ panic(reason)
9028
+ }
9029
+ return v
9030
+ }
9031
+
9032
+ func TnPromiseReject[T any](reason interface{}) chan T {
9033
+ ch := make(chan T)
9034
+ TnReject(ch, reason)
9035
+ return ch
9036
+ }
9037
+
9038
+ func TnPromiseCatch[T any, E any](p chan T, onRejected func(E)) chan T {
9039
+ out := make(chan T, 1)
9040
+ go func() {
9041
+ v, reason, rejected := tnSettle(p)
9042
+ if rejected {
9043
+ e, _ := reason.(E)
9044
+ onRejected(e)
9045
+ }
9046
+ out <- v
9047
+ close(out)
9048
+ }()
9049
+ return out
9050
+ }
9051
+
9052
+ func TnPromiseCatchValue[T any, E any](p chan T, onRejected func(E) T) chan T {
9053
+ out := make(chan T, 1)
9054
+ go func() {
9055
+ v, reason, rejected := tnSettle(p)
9056
+ if rejected {
9057
+ e, _ := reason.(E)
9058
+ v = onRejected(e)
9059
+ }
9060
+ out <- v
9061
+ close(out)
9062
+ }()
9063
+ return out
9064
+ }
9065
+
9066
+ func TnPromiseFinally[T any](p chan T, onFinally func()) chan T {
9067
+ out := make(chan T, 1)
9068
+ go func() {
9069
+ v, reason, rejected := tnSettle(p)
9070
+ onFinally()
9071
+ if rejected {
9072
+ TnReject(out, reason)
9073
+ return
9074
+ }
9075
+ out <- v
9076
+ close(out)
9077
+ }()
9078
+ return out
9079
+ }
9080
+
9081
+ // Promise.race: the first promise to settle, fulfilled or rejected
9082
+ func TnPromiseRace[T any](chs []chan T) chan T {
9083
+ out := make(chan T, 1)
9084
+ var once sync.Once
9085
+ for _, ch := range chs {
9086
+ go func(ch chan T) {
9087
+ v, reason, rejected := tnSettle(ch)
9088
+ once.Do(func() {
9089
+ if rejected {
9090
+ TnReject(out, reason)
9091
+ return
9092
+ }
9093
+ out <- v
9094
+ close(out)
9095
+ })
9096
+ }(ch)
9097
+ }
9098
+ return out
9099
+ }
9100
+
9101
+ type TnSettled[T any] struct {
9102
+ status string
9103
+ value T
9104
+ reason interface{}
9105
+ }
9106
+
9107
+ func TnPromiseAllSettled[T any](chs []chan T) chan []*TnSettled[T] {
9108
+ out := make(chan []*TnSettled[T], 1)
9109
+ go func() {
9110
+ results := make([]*TnSettled[T], len(chs))
9111
+ for i, ch := range chs {
9112
+ v, reason, rejected := tnSettle(ch)
9113
+ if rejected {
9114
+ results[i] = &TnSettled[T]{status: "rejected", reason: reason}
9115
+ } else {
9116
+ results[i] = &TnSettled[T]{status: "fulfilled", value: v}
9117
+ }
9118
+ }
9119
+ out <- results
9120
+ close(out)
9121
+ }()
9122
+ return out
9123
+ }
9124
+
9125
+ // Promise.any: the first promise to fulfil; rejected when all of them reject
9126
+ func TnPromiseAny[T any](chs []chan T) chan T {
9127
+ out := make(chan T, 1)
9128
+ go func() {
9129
+ values := make(chan T, len(chs))
9130
+ failures := make(chan interface{}, len(chs))
9131
+ for _, ch := range chs {
9132
+ go func(ch chan T) {
9133
+ v, reason, rejected := tnSettle(ch)
9134
+ if rejected {
9135
+ failures <- reason
9136
+ } else {
9137
+ values <- v
9138
+ }
9139
+ }(ch)
9140
+ }
9141
+ for range chs {
9142
+ select {
9143
+ case v := <-values:
9144
+ out <- v
9145
+ close(out)
9146
+ return
9147
+ case <-failures:
9148
+ }
9149
+ }
9150
+ TnReject(out, TnNewError("All promises were rejected"))
9151
+ }()
9152
+ return out
9153
+ }`,
7930
9154
  promiseAll: `// Promise.all: every channel drained in order, results collected
7931
9155
  func TnPromiseAll[T any](chs []chan T) chan []T {
7932
9156
  out := make(chan []T)
7933
9157
  go func() {
7934
9158
  results := make([]T, len(chs))
7935
9159
  for i, ch := range chs {
7936
- results[i] = <-ch
9160
+ v, reason, rejected := tnSettle(ch)
9161
+ if rejected {
9162
+ TnReject(out, reason)
9163
+ return
9164
+ }
9165
+ results[i] = v
7937
9166
  }
7938
9167
  out <- results
7939
9168
  close(out)
@@ -8121,6 +9350,18 @@ function visitImportDeclaration(node) {
8121
9350
  includeLocalImport(resolved.content, resolved.dir, goFileName);
8122
9351
  }
8123
9352
  }
9353
+ // Renamed bindings refer to the original declaration (one Go package)
9354
+ const bindings = node.importClause?.namedBindings;
9355
+ if (bindings && isNamedImports(bindings)) {
9356
+ for (const el of bindings.elements ?? []) {
9357
+ if (!el.propertyName)
9358
+ continue;
9359
+ importAliases.set(el.name.text, getSafeName(el.propertyName.text));
9360
+ const fn = declaredFunctions.get(el.propertyName.text);
9361
+ if (fn)
9362
+ declaredFunctions.set(el.name.text, fn);
9363
+ }
9364
+ }
8124
9365
  return '';
8125
9366
  }
8126
9367
  // Type-only imports contribute nothing to runtime