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 +1320 -79
- package/conformance.md +80 -71
- package/package.json +1 -1
- package/tsparser/main.go +17 -1
- package/tsparser/tsparser.exe +2 -2
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
|
|
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 (
|
|
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 (
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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-
|
|
5015
|
-
toJSON: (obj) => `(${obj}).UTC().Format("2006-01-
|
|
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
|
|
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', '
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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, _ :=
|
|
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, _ :=
|
|
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, _ :=
|
|
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 :=
|
|
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
|
|
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,
|
|
6918
|
-
|
|
6919
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|