typenative 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -20
- package/bin/index.js +242 -204
- package/bin/parse-native.js +3 -2
- package/bin/parse-node.js +18 -2
- package/bin/transpiler.js +2211 -113
- package/conformance.md +96 -87
- package/package.json +1 -1
- package/tsparser/main.go +53 -3
- package/tsparser/tsparser.exe +2 -2
- package/types/typenative-npm.d.ts +7 -3
- package/types/typenative.d.ts +446 -53
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',
|
|
@@ -337,6 +338,8 @@ const interfacePropertyTypes = new Map();
|
|
|
337
338
|
const typeAliases = new Map();
|
|
338
339
|
const enumNames = new Set();
|
|
339
340
|
const enumBaseTypes = new Map();
|
|
341
|
+
// Member types of heterogeneous enums ("E.A" → float64 / string)
|
|
342
|
+
const enumMemberGoTypes = new Map();
|
|
340
343
|
// Maps local TS name → Go qualified name (e.g. 'Println' → 'fmt.Println', 'myFmt' → 'fmt')
|
|
341
344
|
const importAliases = new Map();
|
|
342
345
|
// Tracks static methods per class: Set of "ClassName.methodName" strings
|
|
@@ -389,13 +392,22 @@ const OPERATOR_TEXT = {
|
|
|
389
392
|
function operatorTokenText(token) {
|
|
390
393
|
return OPERATOR_TEXT[token?.kind] ?? token?.kind ?? '';
|
|
391
394
|
}
|
|
392
|
-
function defaultParseFunction(_code) {
|
|
395
|
+
function defaultParseFunction(_code, _tsx) {
|
|
393
396
|
throw new Error('transpileToNative: no parse function injected (options.parse)');
|
|
394
397
|
}
|
|
395
398
|
// Parses via the injected parser and links each node to its parent: the JSON
|
|
396
399
|
// AST carries no parent pointers, but the transpiler walks up via node.parent.
|
|
397
400
|
function parseSource(code) {
|
|
398
|
-
const sourceFile = parseFunction(code);
|
|
401
|
+
const sourceFile = parseFunction(code, currentFileIsTsx);
|
|
402
|
+
stripThisParameters(sourceFile);
|
|
403
|
+
lowerIndexSignatures(sourceFile);
|
|
404
|
+
lowerAmbientDeclarations(sourceFile);
|
|
405
|
+
lowerModuleForms(sourceFile);
|
|
406
|
+
lowerDisposeSymbols(sourceFile);
|
|
407
|
+
lowerNamespaces(sourceFile);
|
|
408
|
+
lowerClassExpressions(sourceFile);
|
|
409
|
+
lowerWeakCollections(sourceFile);
|
|
410
|
+
lowerGlobalThis(sourceFile);
|
|
399
411
|
linkParents(sourceFile, undefined);
|
|
400
412
|
for (const stmt of sourceFile.statements ?? []) {
|
|
401
413
|
if (!stmt.name || !isIdentifier(stmt.name))
|
|
@@ -415,6 +427,289 @@ function parseSource(code) {
|
|
|
415
427
|
}
|
|
416
428
|
return sourceFile;
|
|
417
429
|
}
|
|
430
|
+
// `this: T` parameters only type `this`; they are not real parameters
|
|
431
|
+
function stripThisParameters(node) {
|
|
432
|
+
if (Array.isArray(node.parameters)) {
|
|
433
|
+
node.parameters = node.parameters.filter((p) => !(isIdentifier(p.name) && p.name.text === 'this'));
|
|
434
|
+
}
|
|
435
|
+
for (const child of childNodes(node))
|
|
436
|
+
stripThisParameters(child);
|
|
437
|
+
}
|
|
438
|
+
// { [k: K]: V } and { [K in string]: V } → Record<K, V>; an interface that is
|
|
439
|
+
// only an index signature becomes an alias of that Record
|
|
440
|
+
function lowerIndexSignatures(node) {
|
|
441
|
+
for (const child of childNodes(node))
|
|
442
|
+
lowerIndexSignatures(child);
|
|
443
|
+
const keyKind = node.typeParameter?.constraint?.kind;
|
|
444
|
+
if (node.kind === 'MappedType' && (keyKind === 'StringKeyword' || keyKind === 'NumberKeyword')) {
|
|
445
|
+
const keyType = { kind: keyKind };
|
|
446
|
+
makeRecordReference(node, keyType, node.type);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const members = node.members ?? [];
|
|
450
|
+
// { (a: A): R } / { new (a: A): C } → (a: A) => R / (a: A) => C
|
|
451
|
+
const isSignature = (m) => m.kind === 'CallSignature' || m.kind === 'ConstructSignature';
|
|
452
|
+
if (members.length === 1 && isSignature(members[0]) && (node.kind === 'TypeLiteral' || node.kind === 'InterfaceDeclaration')) {
|
|
453
|
+
const fnType = { kind: 'FunctionType', parameters: members[0].parameters ?? [], type: members[0].type };
|
|
454
|
+
if (node.kind === 'TypeLiteral') {
|
|
455
|
+
for (const key of Object.keys(node))
|
|
456
|
+
if (key !== 'parent')
|
|
457
|
+
delete node[key];
|
|
458
|
+
node.kind = 'FunctionType';
|
|
459
|
+
node.parameters = fnType.parameters;
|
|
460
|
+
node.type = fnType.type;
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
delete node.members;
|
|
464
|
+
delete node.heritageClauses;
|
|
465
|
+
node.kind = 'TypeAliasDeclaration';
|
|
466
|
+
node.type = fnType;
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (members.length !== 1 || members[0].kind !== 'IndexSignature')
|
|
471
|
+
return;
|
|
472
|
+
const signature = members[0];
|
|
473
|
+
if (node.kind === 'TypeLiteral')
|
|
474
|
+
makeRecordReference(node, signature.parameters[0].type, signature.type);
|
|
475
|
+
if (node.kind === 'InterfaceDeclaration') {
|
|
476
|
+
const record = { kind: 'TypeLiteral' };
|
|
477
|
+
makeRecordReference(record, signature.parameters[0].type, signature.type);
|
|
478
|
+
delete node.members;
|
|
479
|
+
delete node.heritageClauses;
|
|
480
|
+
node.kind = 'TypeAliasDeclaration';
|
|
481
|
+
node.type = record;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// Turns node into the type reference Record<keyType, valueType>
|
|
485
|
+
function makeRecordReference(node, keyType, valueType) {
|
|
486
|
+
for (const key of Object.keys(node))
|
|
487
|
+
if (key !== 'parent')
|
|
488
|
+
delete node[key];
|
|
489
|
+
const typeName = { kind: 'Identifier', text: 'Record' };
|
|
490
|
+
node.kind = 'TypeReference';
|
|
491
|
+
node.typeName = typeName;
|
|
492
|
+
node.typeArguments = [keyType, valueType];
|
|
493
|
+
}
|
|
494
|
+
function lowerAmbientDeclarations(node) {
|
|
495
|
+
for (const child of childNodes(node))
|
|
496
|
+
lowerAmbientDeclarations(child);
|
|
497
|
+
if (!Array.isArray(node.statements))
|
|
498
|
+
return;
|
|
499
|
+
const isDeclare = (m) => m.kind === 'DeclareKeyword';
|
|
500
|
+
node.statements = node.statements.filter((statement) => {
|
|
501
|
+
if (statement.kind === 'VariableStatement' && statement.modifiers) {
|
|
502
|
+
// declare / export on variables don't change the emitted Go
|
|
503
|
+
statement.modifiers = statement.modifiers.filter((m) => !isDeclare(m) && m.kind !== 'ExportKeyword');
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
return !(statement.modifiers ?? []).some(isDeclare);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
// Every module form becomes an import declaration (all files share one Go package):
|
|
510
|
+
// export { a, b as c } from './m' → import { a, b as c } from './m'
|
|
511
|
+
// export * from './m' → import './m'
|
|
512
|
+
// import x = require('./m') → import * as x from './m'
|
|
513
|
+
// const x = await import('./m') → import * as x from './m'
|
|
514
|
+
function lowerModuleForms(node) {
|
|
515
|
+
for (const child of childNodes(node))
|
|
516
|
+
lowerModuleForms(child);
|
|
517
|
+
if (!Array.isArray(node.statements))
|
|
518
|
+
return;
|
|
519
|
+
node.statements = node.statements.map((statement) => {
|
|
520
|
+
const namespaceImport = (name, specifier) => ({
|
|
521
|
+
kind: 'ImportDeclaration',
|
|
522
|
+
moduleSpecifier: specifier,
|
|
523
|
+
importClause: { kind: 'ImportClause', namedBindings: { kind: 'NamespaceImport', name } }
|
|
524
|
+
});
|
|
525
|
+
if (statement.kind === 'ExportDeclaration' && statement.moduleSpecifier && !statement.isTypeOnly) {
|
|
526
|
+
const clause = statement.exportClause;
|
|
527
|
+
if (clause?.kind === 'NamespaceExport')
|
|
528
|
+
return namespaceImport(clause.name, statement.moduleSpecifier);
|
|
529
|
+
const importClause = clause
|
|
530
|
+
? { kind: 'ImportClause', namedBindings: { kind: 'NamedImports', elements: clause.elements ?? [] } }
|
|
531
|
+
: undefined;
|
|
532
|
+
return { kind: 'ImportDeclaration', moduleSpecifier: statement.moduleSpecifier, importClause };
|
|
533
|
+
}
|
|
534
|
+
if (statement.kind === 'ImportEqualsDeclaration' && statement.moduleReference?.kind === 'ExternalModuleReference') {
|
|
535
|
+
return namespaceImport(statement.name, statement.moduleReference.expression);
|
|
536
|
+
}
|
|
537
|
+
const declarations = statement.kind === 'VariableStatement' ? (statement.declarationList?.declarations ?? []) : [];
|
|
538
|
+
const init = declarations.length === 1 ? declarations[0].initializer : undefined;
|
|
539
|
+
const call = init?.kind === 'AwaitExpression' ? init.expression : undefined;
|
|
540
|
+
if (call?.kind === 'CallExpression' && call.expression?.kind === 'ImportKeyword' && isStringLiteral(call.arguments?.[0]) && isIdentifier(declarations[0].name)) {
|
|
541
|
+
return namespaceImport(declarations[0].name, call.arguments[0]);
|
|
542
|
+
}
|
|
543
|
+
return statement;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
// Names whose identifier is a property / member name, not a reference
|
|
547
|
+
const MEMBER_NAME_PARENTS = new Set([
|
|
548
|
+
'PropertyAccessExpression', 'PropertyAssignment', 'PropertySignature', 'MethodSignature',
|
|
549
|
+
'PropertyDeclaration', 'MethodDeclaration', 'GetAccessor', 'SetAccessor', 'EnumMember'
|
|
550
|
+
]);
|
|
551
|
+
// Top-level names a statement list declares
|
|
552
|
+
function getDeclaredNames(statements) {
|
|
553
|
+
const names = [];
|
|
554
|
+
for (const statement of statements) {
|
|
555
|
+
if (statement.kind === 'VariableStatement') {
|
|
556
|
+
for (const d of statement.declarationList?.declarations ?? [])
|
|
557
|
+
if (isIdentifier(d.name))
|
|
558
|
+
names.push(d.name.text);
|
|
559
|
+
}
|
|
560
|
+
else if (isIdentifier(statement.name)) {
|
|
561
|
+
names.push(statement.name.text);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return names;
|
|
565
|
+
}
|
|
566
|
+
// Renames references to the given names (not property/member names) to prefix_name
|
|
567
|
+
function renameIdentifiers(node, names, prefix) {
|
|
568
|
+
for (const key of Object.keys(node)) {
|
|
569
|
+
if (key === 'parent')
|
|
570
|
+
continue;
|
|
571
|
+
const value = node[key];
|
|
572
|
+
const children = Array.isArray(value) ? value : value && typeof value === 'object' ? [value] : [];
|
|
573
|
+
for (const child of children) {
|
|
574
|
+
if (!child || typeof child.kind !== 'string')
|
|
575
|
+
continue;
|
|
576
|
+
if (isIdentifier(child) && names.has(child.text)) {
|
|
577
|
+
if (key === 'name' && MEMBER_NAME_PARENTS.has(node.kind))
|
|
578
|
+
continue;
|
|
579
|
+
if (key === 'right' && node.kind === 'QualifiedName')
|
|
580
|
+
continue;
|
|
581
|
+
if (node.kind === 'ShorthandPropertyAssignment') {
|
|
582
|
+
// { unit } keeps its key: { unit: N_unit }
|
|
583
|
+
node.kind = 'PropertyAssignment';
|
|
584
|
+
const initializer = { kind: 'Identifier', text: `${prefix}_${child.text}` };
|
|
585
|
+
node.initializer = initializer;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
child.text = `${prefix}_${child.text}`;
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
renameIdentifiers(child, names, prefix);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Flattens the namespaces in a statement list; returns the namespace names found
|
|
597
|
+
// (nested ones as Outer_Inner)
|
|
598
|
+
function flattenNamespaces(statements) {
|
|
599
|
+
const out = [];
|
|
600
|
+
const namespaces = [];
|
|
601
|
+
for (const statement of statements) {
|
|
602
|
+
const isDeclare = (statement.modifiers ?? []).some((m) => m.kind === 'DeclareKeyword');
|
|
603
|
+
if (statement.kind !== 'ModuleDeclaration' || !isIdentifier(statement.name) || isDeclare) {
|
|
604
|
+
out.push(statement);
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const prefix = statement.name.text;
|
|
608
|
+
// namespace A.B { } nests a ModuleDeclaration as the body
|
|
609
|
+
const body = statement.body?.kind === 'ModuleDeclaration' ? [statement.body] : (statement.body?.statements ?? []);
|
|
610
|
+
const inner = flattenNamespaces(body);
|
|
611
|
+
rewriteNamespaceAccess(inner.statements, new Set(inner.namespaces));
|
|
612
|
+
const names = new Set([...getDeclaredNames(inner.statements), ...inner.namespaces]);
|
|
613
|
+
for (const child of inner.statements) {
|
|
614
|
+
const wrapper = { kind: 'Block', statements: [child] };
|
|
615
|
+
renameIdentifiers(wrapper, names, prefix);
|
|
616
|
+
}
|
|
617
|
+
namespaces.push(prefix);
|
|
618
|
+
for (const n of inner.namespaces)
|
|
619
|
+
namespaces.push(`${prefix}_${n}`);
|
|
620
|
+
out.push(...inner.statements);
|
|
621
|
+
}
|
|
622
|
+
return { statements: out, namespaces };
|
|
623
|
+
}
|
|
624
|
+
// N.x / N.Inner.x (expressions) and N.T (types) → the flattened identifiers
|
|
625
|
+
function rewriteNamespaceAccess(nodes, namespaces) {
|
|
626
|
+
const rewrite = (node) => {
|
|
627
|
+
for (const key of Object.keys(node)) {
|
|
628
|
+
if (key === 'parent')
|
|
629
|
+
continue;
|
|
630
|
+
const value = node[key];
|
|
631
|
+
if (Array.isArray(value))
|
|
632
|
+
node[key] = value.map((v) => (v && typeof v.kind === 'string' ? rewrite(v) : v));
|
|
633
|
+
else if (value && typeof value.kind === 'string')
|
|
634
|
+
node[key] = rewrite(value);
|
|
635
|
+
}
|
|
636
|
+
const left = node.kind === 'PropertyAccessExpression' ? node.expression : node.kind === 'QualifiedName' ? node.left : undefined;
|
|
637
|
+
const right = node.kind === 'PropertyAccessExpression' ? node.name : node.kind === 'QualifiedName' ? node.right : undefined;
|
|
638
|
+
if (isIdentifier(left) && namespaces.has(left.text) && isIdentifier(right)) {
|
|
639
|
+
const flattened = { kind: 'Identifier', text: `${left.text}_${right.text}` };
|
|
640
|
+
return flattened;
|
|
641
|
+
}
|
|
642
|
+
return node;
|
|
643
|
+
};
|
|
644
|
+
for (let i = 0; i < nodes.length; i++)
|
|
645
|
+
nodes[i] = rewrite(nodes[i]);
|
|
646
|
+
}
|
|
647
|
+
function lowerNamespaces(sourceFile) {
|
|
648
|
+
const flat = flattenNamespaces(sourceFile.statements ?? []);
|
|
649
|
+
sourceFile.statements = flat.statements;
|
|
650
|
+
if (flat.namespaces.length > 0)
|
|
651
|
+
rewriteNamespaceAccess(sourceFile.statements, new Set(flat.namespaces));
|
|
652
|
+
}
|
|
653
|
+
const DISPOSE_SYMBOLS = { dispose: '__dispose', asyncDispose: '__asyncDispose' };
|
|
654
|
+
function getDisposeSymbolName(expr) {
|
|
655
|
+
if (!isPropertyAccessExpression(expr) || !isIdentifier(expr.expression) || expr.expression.text !== 'Symbol')
|
|
656
|
+
return undefined;
|
|
657
|
+
return DISPOSE_SYMBOLS[expr.name.text];
|
|
658
|
+
}
|
|
659
|
+
// [Symbol.dispose]() { … } → __dispose() { … }; x[Symbol.dispose]() → x.__dispose()
|
|
660
|
+
function lowerDisposeSymbols(node) {
|
|
661
|
+
for (const child of childNodes(node))
|
|
662
|
+
lowerDisposeSymbols(child);
|
|
663
|
+
if (node.name?.kind === 'ComputedPropertyName') {
|
|
664
|
+
const name = getDisposeSymbolName(node.name.expression);
|
|
665
|
+
if (name) {
|
|
666
|
+
const identifier = { kind: 'Identifier', text: name };
|
|
667
|
+
node.name = identifier;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (node.kind === 'ElementAccessExpression') {
|
|
671
|
+
const name = getDisposeSymbolName(node.argumentExpression);
|
|
672
|
+
if (name) {
|
|
673
|
+
const identifier = { kind: 'Identifier', text: name };
|
|
674
|
+
node.kind = 'PropertyAccessExpression';
|
|
675
|
+
node.name = identifier;
|
|
676
|
+
delete node.argumentExpression;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
// const Point = class { … } → class Point { … } (the variable names the class)
|
|
681
|
+
function lowerClassExpressions(node) {
|
|
682
|
+
for (const child of childNodes(node))
|
|
683
|
+
lowerClassExpressions(child);
|
|
684
|
+
if (!Array.isArray(node.statements))
|
|
685
|
+
return;
|
|
686
|
+
node.statements = node.statements.map((statement) => {
|
|
687
|
+
const declarations = statement.kind === 'VariableStatement' ? (statement.declarationList?.declarations ?? []) : [];
|
|
688
|
+
const initializer = declarations.length === 1 ? declarations[0].initializer : undefined;
|
|
689
|
+
if (initializer?.kind !== 'ClassExpression' || !isIdentifier(declarations[0].name))
|
|
690
|
+
return statement;
|
|
691
|
+
return { ...initializer, kind: 'ClassDeclaration', name: declarations[0].name, modifiers: statement.modifiers };
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
// WeakMap / WeakSet → Map / Set: same API; weakness is not observable
|
|
695
|
+
function lowerWeakCollections(node) {
|
|
696
|
+
for (const child of childNodes(node))
|
|
697
|
+
lowerWeakCollections(child);
|
|
698
|
+
const name = node.kind === 'NewExpression' ? node.expression : node.kind === 'TypeReference' ? node.typeName : undefined;
|
|
699
|
+
if (isIdentifier(name) && (name.text === 'WeakMap' || name.text === 'WeakSet'))
|
|
700
|
+
name.text = name.text.slice(4);
|
|
701
|
+
}
|
|
702
|
+
// globalThis.x (typed) is the global x itself; (globalThis as any) stays a dynamic object
|
|
703
|
+
function lowerGlobalThis(node) {
|
|
704
|
+
for (const key of Object.keys(node)) {
|
|
705
|
+
const child = node[key];
|
|
706
|
+
if (isPropertyAccessExpression(child) && isIdentifier(child.expression) && child.expression.text === 'globalThis') {
|
|
707
|
+
node[key] = child.name;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
for (const child of childNodes(node))
|
|
711
|
+
lowerGlobalThis(child);
|
|
712
|
+
}
|
|
418
713
|
function linkParents(node, parent) {
|
|
419
714
|
node.parent = parent;
|
|
420
715
|
for (const key of Object.keys(node)) {
|
|
@@ -494,9 +789,12 @@ const usedHelpers = new Set();
|
|
|
494
789
|
// per-file import lists can exclude them (helpers are emitted once, in the
|
|
495
790
|
// main file, which carries these imports instead).
|
|
496
791
|
const helperProvidedPackages = new Set();
|
|
792
|
+
// Whether the file being parsed is .tsx
|
|
793
|
+
let currentFileIsTsx = false;
|
|
497
794
|
export function transpileToNative(code, options) {
|
|
498
795
|
fileResolver = options?.readFile ?? null;
|
|
499
796
|
parseFunction = options?.parse ?? defaultParseFunction;
|
|
797
|
+
currentFileIsTsx = !!options?.tsx;
|
|
500
798
|
declaredFunctions.clear();
|
|
501
799
|
declaredInterfaces.clear();
|
|
502
800
|
declaredTypeAliases.clear();
|
|
@@ -542,6 +840,8 @@ export function transpileToNative(code, options) {
|
|
|
542
840
|
const drains = [];
|
|
543
841
|
if (usedHelpers.has('asyncWait'))
|
|
544
842
|
drains.push('__tnWaitGroup.Wait()');
|
|
843
|
+
if (usedHelpers.has('timerWait'))
|
|
844
|
+
drains.unshift('TnRunMicrotasks()');
|
|
545
845
|
if (usedHelpers.has('timerWait'))
|
|
546
846
|
drains.push('__tnTimers.Wait()');
|
|
547
847
|
const drainCode = drains.length > 0 ? `\n\t${drains.join('\n\t')}` : '';
|
|
@@ -600,6 +900,13 @@ function visit(node, options = {}) {
|
|
|
600
900
|
.filter((n) => !!n)
|
|
601
901
|
.join(options.inline ? '' : '\n\t');
|
|
602
902
|
}
|
|
903
|
+
else if (isJsxNode(node)) {
|
|
904
|
+
return visitJsx(node);
|
|
905
|
+
}
|
|
906
|
+
else if (isIdentifier(node) && node.text === 'globalThis') {
|
|
907
|
+
useHelper('globalThis');
|
|
908
|
+
return 'TnGlobalThis';
|
|
909
|
+
}
|
|
603
910
|
else if (isIdentifier(node)) {
|
|
604
911
|
if (isFieldNameIdentifier(node))
|
|
605
912
|
return goFieldName(node.text);
|
|
@@ -619,6 +926,9 @@ function visit(node, options = {}) {
|
|
|
619
926
|
const goAlias = importAliases.get(node.text);
|
|
620
927
|
if (goAlias)
|
|
621
928
|
return goAlias;
|
|
929
|
+
// A class passed as a value (make(Box)) is its constructor function
|
|
930
|
+
if (classNames.has(node.text) && isValuePosition(node))
|
|
931
|
+
return `New${getSafeName(node.text)}`;
|
|
622
932
|
// Array references: reads go through the shared slice-header pointer
|
|
623
933
|
if (referenceArrays.has(node.text)) {
|
|
624
934
|
return `(*${getSafeName(node.text)})`;
|
|
@@ -640,6 +950,18 @@ function visit(node, options = {}) {
|
|
|
640
950
|
else if (isTemplateExpression(node)) {
|
|
641
951
|
return visitTemplateExpression(node);
|
|
642
952
|
}
|
|
953
|
+
else if (node.kind === 'TaggedTemplateExpression' && isStringRawTag(node.tag)) {
|
|
954
|
+
// String.raw`a\n${x}`: the raw source text with the values interpolated
|
|
955
|
+
const template = node.template;
|
|
956
|
+
const raw = (part) => toGoStringLiteral(part.rawText ?? part.text);
|
|
957
|
+
if (template.kind === 'NoSubstitutionTemplateLiteral')
|
|
958
|
+
return raw(template);
|
|
959
|
+
const pieces = [raw(template.head)];
|
|
960
|
+
for (const span of template.templateSpans ?? []) {
|
|
961
|
+
pieces.push(jsStringOf(visit(span.expression), inferExpressionType(span.expression)), raw(span.literal));
|
|
962
|
+
}
|
|
963
|
+
return `(${pieces.join(' + ')})`;
|
|
964
|
+
}
|
|
643
965
|
else if (node.kind === 'TaggedTemplateExpression') {
|
|
644
966
|
// tag`x${2}` → tag(["x", ""], 2)
|
|
645
967
|
const tag = visit(node.tag);
|
|
@@ -684,7 +1006,7 @@ function visit(node, options = {}) {
|
|
|
684
1006
|
// backtracking engine — both live in the TnRegex wrapper
|
|
685
1007
|
if (needsStatefulRegex(pattern, flags)) {
|
|
686
1008
|
useHelper('fancyRegex');
|
|
687
|
-
return `TnRegexCompile("${goPattern}", ${flags
|
|
1009
|
+
return `TnRegexCompile("${goPattern}", "${flags}", ${toGoStringLiteral(pattern)})`;
|
|
688
1010
|
}
|
|
689
1011
|
importedPackages.add('regexp');
|
|
690
1012
|
const goFlags = jsRegexFlagsToGo(flags);
|
|
@@ -735,16 +1057,21 @@ function visit(node, options = {}) {
|
|
|
735
1057
|
return `${visit(node.expression)}[int(${visit(node.argumentExpression)})]`;
|
|
736
1058
|
}
|
|
737
1059
|
else if (isPropertyAccessExpression(node)) {
|
|
1060
|
+
const regexProperty = visitRegexProperty(node);
|
|
1061
|
+
if (regexProperty)
|
|
1062
|
+
return regexProperty;
|
|
738
1063
|
// import.meta.url — the file URL of the running program
|
|
1064
|
+
if (isNewTargetNode(node.expression) && node.name.text === 'name')
|
|
1065
|
+
return '__newTarget';
|
|
739
1066
|
if (node.expression.kind === 'MetaProperty' && node.name.text === 'url') {
|
|
740
1067
|
useHelper('moduleUrl');
|
|
741
1068
|
return 'TnModuleURL()';
|
|
742
1069
|
}
|
|
743
|
-
if (
|
|
1070
|
+
if (hasDynamicProperties(node.expression) && node.name.text === 'length' && !isCallee(node)) {
|
|
744
1071
|
useHelper('dynamic');
|
|
745
1072
|
return `TnLength(${visit(node.expression)})`;
|
|
746
1073
|
}
|
|
747
|
-
if (
|
|
1074
|
+
if (hasDynamicProperties(node.expression) && !isCallee(node) && !isProcessEnv(node.expression)) {
|
|
748
1075
|
useHelper('dynamic');
|
|
749
1076
|
return `TnGet(${visit(node.expression)}, "${node.name.text}")`;
|
|
750
1077
|
}
|
|
@@ -953,6 +1280,11 @@ function visit(node, options = {}) {
|
|
|
953
1280
|
if (referenceArrays.has(node.name.text) && type.startsWith('[]')) {
|
|
954
1281
|
type = variableGoTypes.get(node.name.text);
|
|
955
1282
|
}
|
|
1283
|
+
// const v = undefined / void 0: Go cannot infer a type from nil
|
|
1284
|
+
if (type === ':' && inferredType === 'nil') {
|
|
1285
|
+
type = 'interface{}';
|
|
1286
|
+
setVarGoType(node.name.text, 'interface{}');
|
|
1287
|
+
}
|
|
956
1288
|
}
|
|
957
1289
|
let initializer = node.initializer ? `= ${visit(node.initializer)}` : '';
|
|
958
1290
|
// Wrap non-nil values assigned to nullable primitive pointer types
|
|
@@ -981,6 +1313,13 @@ function visit(node, options = {}) {
|
|
|
981
1313
|
return initializer ? `${name} ${initializer}` : '';
|
|
982
1314
|
}
|
|
983
1315
|
const declName = isIdentifier(node.name) ? getSafeName(node.name.text) : visit(node.name);
|
|
1316
|
+
// const f = (…) => … f(…) … / const id = setInterval(() => clearInterval(id)):
|
|
1317
|
+
// a Go closure can't see the variable in its own :=, so declare first, then assign
|
|
1318
|
+
if (type === ':' &&
|
|
1319
|
+
isIdentifier(node.name) &&
|
|
1320
|
+
containsNode(node.initializer, (n) => isIdentifier(n) && n.text === node.name.text && !isFieldNameIdentifier(n))) {
|
|
1321
|
+
return `var ${declName} ${inferExpressionType(node.initializer)}\n\t\t${declName} ${initializer}`;
|
|
1322
|
+
}
|
|
984
1323
|
return `${type === ':' ? '' : 'var '}${declName} ${type}${type === ':' ? '' : ' '}${initializer}`;
|
|
985
1324
|
}
|
|
986
1325
|
else if (isCallExpression(node)) {
|
|
@@ -994,6 +1333,9 @@ function visit(node, options = {}) {
|
|
|
994
1333
|
return visitNullablePrimitiveOptionalCall(node, visit(node.expression.expression), receiverType);
|
|
995
1334
|
}
|
|
996
1335
|
}
|
|
1336
|
+
const recordCall = visitRecordFunctionCall(node);
|
|
1337
|
+
if (recordCall)
|
|
1338
|
+
return recordCall;
|
|
997
1339
|
const dynamicCall = visitDynamicMethodCall(node);
|
|
998
1340
|
if (dynamicCall)
|
|
999
1341
|
return dynamicCall;
|
|
@@ -1020,8 +1362,8 @@ function visit(node, options = {}) {
|
|
|
1020
1362
|
const callback = visit((node.arguments ?? [])[0]);
|
|
1021
1363
|
const delayNode = (node.arguments ?? [])[1];
|
|
1022
1364
|
const delay = isNumericLiteral(delayNode) ? delayNode.text : visit(delayNode);
|
|
1023
|
-
// Registered on the wait group so the process lives until it fires
|
|
1024
|
-
return `
|
|
1365
|
+
// Registered on the wait group so the process lives until it fires (or is cleared)
|
|
1366
|
+
return `TnSetTimeout(${delay ?? '0'}, func() { (${callback.trim()})() })`;
|
|
1025
1367
|
}
|
|
1026
1368
|
// p.then(cb) on a channel-based Promise: wait for the value, run the callback
|
|
1027
1369
|
if (isPropertyAccessExpression(node.expression) &&
|
|
@@ -1075,6 +1417,15 @@ function visit(node, options = {}) {
|
|
|
1075
1417
|
if (arrayHigherOrderCall) {
|
|
1076
1418
|
return arrayHigherOrderCall;
|
|
1077
1419
|
}
|
|
1420
|
+
const promiseCall = visitPromiseCall(node);
|
|
1421
|
+
if (promiseCall)
|
|
1422
|
+
return promiseCall;
|
|
1423
|
+
const functionCall = visitFunctionCallApply(node);
|
|
1424
|
+
if (functionCall)
|
|
1425
|
+
return functionCall;
|
|
1426
|
+
const objectCall = visitObjectCall(node);
|
|
1427
|
+
if (objectCall)
|
|
1428
|
+
return objectCall;
|
|
1078
1429
|
const caller = visit(node.expression);
|
|
1079
1430
|
const safeCaller = getSafeName(caller);
|
|
1080
1431
|
const typeArgs = getTypeArguments((node.typeArguments ?? []));
|
|
@@ -1086,6 +1437,7 @@ function visit(node, options = {}) {
|
|
|
1086
1437
|
if (isPropertyAccessExpression(node.expression)) {
|
|
1087
1438
|
objectType = resolveExpressionType(node.expression.expression);
|
|
1088
1439
|
}
|
|
1440
|
+
currentCallArgTypes = (node.arguments ?? []).map((a) => inferExpressionType(a));
|
|
1089
1441
|
currentReceiverGoType = isPropertyAccessExpression(node.expression)
|
|
1090
1442
|
? inferExpressionType(node.expression.expression)
|
|
1091
1443
|
: undefined;
|
|
@@ -1106,6 +1458,15 @@ function visit(node, options = {}) {
|
|
|
1106
1458
|
else if (isPostfixUnaryExpression(node)) {
|
|
1107
1459
|
return visitIncrementOrDecrement(node, false);
|
|
1108
1460
|
}
|
|
1461
|
+
else if (node.kind === 'VoidExpression') {
|
|
1462
|
+
// void expr evaluates expr and gives undefined
|
|
1463
|
+
if (isNumericLiteral(node.expression))
|
|
1464
|
+
return 'nil';
|
|
1465
|
+
return `func() interface{} { _ = ${visit(node.expression)}; return nil }()`;
|
|
1466
|
+
}
|
|
1467
|
+
else if (node.kind === 'SatisfiesExpression') {
|
|
1468
|
+
return visit(node.expression);
|
|
1469
|
+
}
|
|
1109
1470
|
else if (node.kind === 'DeleteExpression') {
|
|
1110
1471
|
return visitDelete(node);
|
|
1111
1472
|
}
|
|
@@ -1254,6 +1615,16 @@ function visit(node, options = {}) {
|
|
|
1254
1615
|
return op === '==' ? eq : `!(${eq})`;
|
|
1255
1616
|
}
|
|
1256
1617
|
}
|
|
1618
|
+
// new.target === C → the target class name is C's
|
|
1619
|
+
if ((op === '==' || op === '!=') && (isNewTargetNode(node.left) || isNewTargetNode(node.right))) {
|
|
1620
|
+
const other = isNewTargetNode(node.left) ? node.right : node.left;
|
|
1621
|
+
return `__newTarget ${op} ${isIdentifier(other) ? toGoStringLiteral(other.text) : '""'}`;
|
|
1622
|
+
}
|
|
1623
|
+
// obj[key] = v on any
|
|
1624
|
+
if (op === '=' && isElementAccessExpression(node.left) && isDynamicValue(node.left.expression)) {
|
|
1625
|
+
useHelper('dynamic');
|
|
1626
|
+
return `TnSetIndex(${visit(node.left.expression)}, ${visit(node.left.argumentExpression)}, ${visit(node.right)})`;
|
|
1627
|
+
}
|
|
1257
1628
|
if (op === '=' && isPropertyAccessExpression(node.left) && isDynamicValue(node.left.expression)) {
|
|
1258
1629
|
useHelper('dynamic');
|
|
1259
1630
|
return `TnSet(${visit(node.left.expression)}, "${node.left.name.text}", ${visit(node.right)})`;
|
|
@@ -1328,6 +1699,10 @@ function visit(node, options = {}) {
|
|
|
1328
1699
|
return '';
|
|
1329
1700
|
}
|
|
1330
1701
|
else if (isAwaitExpression(node)) {
|
|
1702
|
+
if (inferExpressionType(node.expression)?.startsWith('chan ')) {
|
|
1703
|
+
useHelper('promise');
|
|
1704
|
+
return `TnAwait(${visit(node.expression)})`;
|
|
1705
|
+
}
|
|
1331
1706
|
return `<-${visit(node.expression)}`;
|
|
1332
1707
|
}
|
|
1333
1708
|
else if (isVariableDeclarationList(node)) {
|
|
@@ -1342,11 +1717,15 @@ function visit(node, options = {}) {
|
|
|
1342
1717
|
asyncFunctionNames.has(exprStmt.expression.text) &&
|
|
1343
1718
|
!emittingModuleFile) {
|
|
1344
1719
|
useHelper('asyncWait');
|
|
1345
|
-
|
|
1720
|
+
useHelper('promise');
|
|
1721
|
+
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
1722
|
}
|
|
1347
1723
|
return visit(node.expression) + (options.inline ? '' : ';\n\t');
|
|
1348
1724
|
}
|
|
1349
1725
|
else if (isForStatement(node)) {
|
|
1726
|
+
const multi = visitMultiForHeader(node);
|
|
1727
|
+
if (multi)
|
|
1728
|
+
return multi;
|
|
1350
1729
|
return `for ${visit(node.initializer, { inline: true })}; ${node.condition ? toGoCondition(node.condition) : ''}; ${visit(node.incrementor, { inline: true })}${visitLoopBody(node.statement)}`;
|
|
1351
1730
|
}
|
|
1352
1731
|
else if (isForInStatement(node)) {
|
|
@@ -1489,6 +1868,9 @@ function visit(node, options = {}) {
|
|
|
1489
1868
|
return `return ${value}` + terminator;
|
|
1490
1869
|
}
|
|
1491
1870
|
else if (isFunctionDeclaration(node) || isFunctionExpression(node)) {
|
|
1871
|
+
// overload signatures have no body: only the implementation is emitted
|
|
1872
|
+
if (!node.body)
|
|
1873
|
+
return '';
|
|
1492
1874
|
if (options.addFunctionOutside) {
|
|
1493
1875
|
outsideNodes.push(node);
|
|
1494
1876
|
return '';
|
|
@@ -1503,7 +1885,7 @@ function visit(node, options = {}) {
|
|
|
1503
1885
|
if (options.isOutside) {
|
|
1504
1886
|
// async functions run their body in a goroutine sending over a channel
|
|
1505
1887
|
const isAsync = (node.modifiers ?? []).some((m) => m.kind === 'AsyncKeyword');
|
|
1506
|
-
if (isAsync && node.name) {
|
|
1888
|
+
if (isAsync && node.name && !node.asteriskToken) {
|
|
1507
1889
|
return emitChannelFunction(node, true);
|
|
1508
1890
|
}
|
|
1509
1891
|
if (node.asteriskToken && node.name) {
|
|
@@ -1535,7 +1917,7 @@ function visit(node, options = {}) {
|
|
|
1535
1917
|
}
|
|
1536
1918
|
if (!isBlock(node.body)) {
|
|
1537
1919
|
// void-style calls (console.*, assert) emit statements, not values
|
|
1538
|
-
if (isVoidStatementCall(node.body)) {
|
|
1920
|
+
if (isVoidStatementCall(node.body) || !inferredRetType) {
|
|
1539
1921
|
return `func(${parameterInfo.signature})${returnType} { ${visit(node.body)}; }`;
|
|
1540
1922
|
}
|
|
1541
1923
|
return `func(${parameterInfo.signature})${returnType} { return ${visit(node.body)}; }`;
|
|
@@ -1547,6 +1929,10 @@ function visit(node, options = {}) {
|
|
|
1547
1929
|
else if (node.kind === 'ThisKeyword') {
|
|
1548
1930
|
if (thisOverrideName && getEnclosingFunction(node) === thisOverrideFn)
|
|
1549
1931
|
return thisOverrideName;
|
|
1932
|
+
if (isThisOfPlainFunction(node)) {
|
|
1933
|
+
useHelper('decorators');
|
|
1934
|
+
return 'TnThis';
|
|
1935
|
+
}
|
|
1550
1936
|
return 'self';
|
|
1551
1937
|
}
|
|
1552
1938
|
else if (node.kind === 'PrivateIdentifier') {
|
|
@@ -1558,6 +1944,8 @@ function visit(node, options = {}) {
|
|
|
1558
1944
|
enumNames.add(enumName);
|
|
1559
1945
|
enumBaseTypes.set(enumName, getEnumBaseType(node));
|
|
1560
1946
|
if (options.addFunctionOutside) {
|
|
1947
|
+
if (enumHasComputedMember(node))
|
|
1948
|
+
return visitEnumDeclaration(node, true);
|
|
1561
1949
|
outsideNodes.push(node);
|
|
1562
1950
|
return '';
|
|
1563
1951
|
}
|
|
@@ -1645,7 +2033,7 @@ function visit(node, options = {}) {
|
|
|
1645
2033
|
return `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}`;
|
|
1646
2034
|
}
|
|
1647
2035
|
const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
|
|
1648
|
-
return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}`;
|
|
2036
|
+
return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}${extendedInterfaces.length === 0 ? emitInterfaceAdapter(node) : ''}`;
|
|
1649
2037
|
}
|
|
1650
2038
|
else if (isClassDeclaration(node)) {
|
|
1651
2039
|
if (options.addFunctionOutside) {
|
|
@@ -1729,7 +2117,7 @@ function visit(node, options = {}) {
|
|
|
1729
2117
|
}
|
|
1730
2118
|
classPropertyTypes.set(className, properties);
|
|
1731
2119
|
classMethodReturnTypes.set(className, methods);
|
|
1732
|
-
return
|
|
2120
|
+
return visitClassDecorators(node);
|
|
1733
2121
|
}
|
|
1734
2122
|
const name = visit(node.name);
|
|
1735
2123
|
const typeParams = getTypeParameters(node.typeParameters);
|
|
@@ -1785,7 +2173,7 @@ function visit(node, options = {}) {
|
|
|
1785
2173
|
const args = (s.expression.arguments ?? []).map((a) => visit(a));
|
|
1786
2174
|
if (extendsError)
|
|
1787
2175
|
return `self.message = ${args[0] ?? '""'}\n\t\t`;
|
|
1788
|
-
return `self.${goFieldName(parentClass)} =
|
|
2176
|
+
return `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, args)}\n\t\t`;
|
|
1789
2177
|
}
|
|
1790
2178
|
return visit(s);
|
|
1791
2179
|
})
|
|
@@ -1794,17 +2182,19 @@ function visit(node, options = {}) {
|
|
|
1794
2182
|
.map((p) => `self.${goFieldName(p.name.text)} = ${visit(p.name)}\n\t\t`)
|
|
1795
2183
|
.join('');
|
|
1796
2184
|
const parentInit = parentClass && !extendsError && !superCalledInCtor
|
|
1797
|
-
? `self.${goFieldName(parentClass)} =
|
|
2185
|
+
? `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, [])}\n\t\t`
|
|
1798
2186
|
: '';
|
|
1799
|
-
|
|
2187
|
+
const ctorBody = `{\n\t\tself := &${name}${typeParamNames}{}\n\t\t${ctorParameterInfo.prefixBlockContent}${parentInit}${fieldInitializers(node)}${parameterAssignments}${bodyStatements}return self;\n\t}`;
|
|
2188
|
+
result += emitConstructor(name, typeParams, typeParamNames, ctorParameterInfo.signature, ctorBody);
|
|
1800
2189
|
}
|
|
1801
2190
|
else if (extendsError) {
|
|
1802
2191
|
// class X extends Error {} — the constructor takes the message
|
|
1803
2192
|
result += `func New${name}${typeParams}(message string) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\tself.message = message\n\t\t${fieldInitializers(node)}return self\n\t}\n\n`;
|
|
1804
2193
|
}
|
|
1805
2194
|
else {
|
|
1806
|
-
const parentInit = parentClass && !extendsError ? `self.${goFieldName(parentClass)} =
|
|
1807
|
-
|
|
2195
|
+
const parentInit = parentClass && !extendsError ? `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, [])}\n\t\t` : '';
|
|
2196
|
+
const ctorBody = `{\n\t\tself := &${name}${typeParamNames}{}\n\t\t${parentInit}${fieldInitializers(node)}return self\n\t}`;
|
|
2197
|
+
result += emitConstructor(name, typeParams, typeParamNames, '', ctorBody);
|
|
1808
2198
|
}
|
|
1809
2199
|
// Inherited concrete methods are copied onto the subclass: Go embedding
|
|
1810
2200
|
// promotes the parent's receiver, which would break `this` dispatch
|
|
@@ -1856,6 +2246,10 @@ function visit(node, options = {}) {
|
|
|
1856
2246
|
if (!methodName || ownMethodNames.has(methodName))
|
|
1857
2247
|
continue;
|
|
1858
2248
|
ownMethodNames.add(methodName);
|
|
2249
|
+
if (getDecorators(member).length > 0) {
|
|
2250
|
+
copiedMethods.push(emitDecoratedMethod(name, typeParamNames, member, visit(ancestor.name)));
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
1859
2253
|
const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
1860
2254
|
const returnType = member.type ? ` ${getType(member.type)}` : '';
|
|
1861
2255
|
copiedMethods.push(`func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n`);
|
|
@@ -1902,7 +2296,10 @@ function visit(node, options = {}) {
|
|
|
1902
2296
|
continue; // abstract method — implemented by subclasses
|
|
1903
2297
|
if (isAbstractClass && !isStatic)
|
|
1904
2298
|
continue; // only reachable via subclasses
|
|
1905
|
-
if (
|
|
2299
|
+
if (getDecorators(member).length > 0) {
|
|
2300
|
+
result += emitDecoratedMethod(name, typeParamNames, member, name);
|
|
2301
|
+
}
|
|
2302
|
+
else if (isStatic) {
|
|
1906
2303
|
// Static methods become package-level functions named ClassName_methodName
|
|
1907
2304
|
result += `func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
|
|
1908
2305
|
}
|
|
@@ -1936,6 +2333,12 @@ function visit(node, options = {}) {
|
|
|
1936
2333
|
result += `var ${name}_${fieldName} ${fieldType}${initializer}\n\n`;
|
|
1937
2334
|
}
|
|
1938
2335
|
}
|
|
2336
|
+
// static { … } blocks run once at program start, after the static fields
|
|
2337
|
+
for (const member of node.members ?? []) {
|
|
2338
|
+
if (member.kind === 'ClassStaticBlockDeclaration' && member.body) {
|
|
2339
|
+
result += `func init() ${visit(member.body).trimEnd()}\n\n`;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
1939
2342
|
return result.trim();
|
|
1940
2343
|
}
|
|
1941
2344
|
else if (isNewExpression(node)) {
|
|
@@ -1983,6 +2386,10 @@ function visit(node, options = {}) {
|
|
|
1983
2386
|
}
|
|
1984
2387
|
const typeArgs = getTypeArguments((node.typeArguments ?? []));
|
|
1985
2388
|
const args = node.arguments ? (node.arguments ?? []).map((a) => visit(a)) : [];
|
|
2389
|
+
// new F(…) where F holds a constructor function (construct signature)
|
|
2390
|
+
if (!classNames.has(className) && inferExpressionType(node.expression)?.startsWith('func(')) {
|
|
2391
|
+
return `${className}(${args.join(', ')})`;
|
|
2392
|
+
}
|
|
1986
2393
|
return `New${className}${typeArgs}(${args.join(', ')})`;
|
|
1987
2394
|
}
|
|
1988
2395
|
else if (isObjectLiteralExpression(node)) {
|
|
@@ -2018,8 +2425,14 @@ function visit(node, options = {}) {
|
|
|
2018
2425
|
if (!typeName || typeName === 'interface{}') {
|
|
2019
2426
|
return visitAnonymousStructLiteral(node);
|
|
2020
2427
|
}
|
|
2021
|
-
// Objects are pointers: the struct type is typeName without its
|
|
2022
|
-
|
|
2428
|
+
// Objects are pointers: the struct type is typeName without its *; generic
|
|
2429
|
+
// types are instantiated with the contextual type arguments (Box<number> → Box[float64])
|
|
2430
|
+
const typeArgs = isTypeReferenceNode(contextualType) && contextualType.typeName.text === typeName.replace(/^\*/, '')
|
|
2431
|
+
? getTypeArguments(contextualType.typeArguments)
|
|
2432
|
+
: '';
|
|
2433
|
+
const structName = typeName.replace(/^\*/, '') + (typeName.endsWith(']') ? '' : typeArgs);
|
|
2434
|
+
if (isMethodInterfaceName(typeName.replace(/^\*/, '')))
|
|
2435
|
+
return visitInterfaceLiteral(node, typeName.replace(/^\*/, ''), typeArgs);
|
|
2023
2436
|
// { ...base, x: 1 } → a copy of *base with fields set
|
|
2024
2437
|
if ((node.properties ?? []).some((p) => p.kind === 'SpreadAssignment')) {
|
|
2025
2438
|
const steps = (node.properties ?? []).map((p) => {
|
|
@@ -2035,7 +2448,7 @@ function visit(node, options = {}) {
|
|
|
2035
2448
|
});
|
|
2036
2449
|
return `func() *${structName} { var __obj ${structName}; ${steps.join('; ')}; return &__obj }()`;
|
|
2037
2450
|
}
|
|
2038
|
-
const fieldTypes = interfacePropertyTypes.get(structName) ?? classPropertyTypes.get(structName);
|
|
2451
|
+
const fieldTypes = typeArgs ? undefined : (interfacePropertyTypes.get(structName) ?? classPropertyTypes.get(structName));
|
|
2039
2452
|
const properties = (node.properties ?? [])
|
|
2040
2453
|
.map((p) => {
|
|
2041
2454
|
if (isPropertyAssignment(p)) {
|
|
@@ -2084,6 +2497,8 @@ function visit(node, options = {}) {
|
|
|
2084
2497
|
// Visits a value going into a slot of Go type `goType`; non-nil values for
|
|
2085
2498
|
// nullable primitives (*string/*float64/*bool) are boxed into pointers
|
|
2086
2499
|
function toGoValueOfType(expr, goType) {
|
|
2500
|
+
if (goType === 'string' && inferExpressionType(expr) === 'TnJsxNode')
|
|
2501
|
+
return `string(${visit(expr)})`;
|
|
2087
2502
|
if (goType &&
|
|
2088
2503
|
((isObjectLiteralExpression(expr) && (expr.properties ?? []).length === 0 && goType.startsWith('*TnMap[')) ||
|
|
2089
2504
|
(isArrayLiteralExpression(expr) && (expr.elements ?? []).length === 0 && goType.startsWith('[]')))) {
|
|
@@ -2137,7 +2552,7 @@ function toGoValueOfType(expr, goType) {
|
|
|
2137
2552
|
return code;
|
|
2138
2553
|
if (isBinaryExpression(expr) &&
|
|
2139
2554
|
expr.operatorToken.kind === 'QuestionQuestionToken' &&
|
|
2140
|
-
|
|
2555
|
+
getNullishEmitType(expr) === goType) {
|
|
2141
2556
|
return code;
|
|
2142
2557
|
}
|
|
2143
2558
|
if (inferExpressionType(expr) !== goType.slice(1))
|
|
@@ -2270,7 +2685,8 @@ function isNarrowableReference(node) {
|
|
|
2270
2685
|
return false;
|
|
2271
2686
|
if (isPropertyAccessExpression(parent) && parent.name === node)
|
|
2272
2687
|
return false;
|
|
2273
|
-
|
|
2688
|
+
// { x } reads the variable x
|
|
2689
|
+
if (parent.name === node && !isShorthandPropertyAssignment(parent))
|
|
2274
2690
|
return false;
|
|
2275
2691
|
if (isBinaryExpression(parent) && parent.left === node) {
|
|
2276
2692
|
const op = parent.operatorToken.kind;
|
|
@@ -2296,7 +2712,12 @@ function alwaysExits(statement) {
|
|
|
2296
2712
|
function visitBlockStatements(statements) {
|
|
2297
2713
|
const added = [];
|
|
2298
2714
|
const parts = [];
|
|
2299
|
-
for (
|
|
2715
|
+
for (let index = 0; index < statements.length; index++) {
|
|
2716
|
+
const statement = statements[index];
|
|
2717
|
+
if (isUsingDeclaration(statement)) {
|
|
2718
|
+
parts.push(visitUsingDeclaration(statement, statements.slice(index + 1)));
|
|
2719
|
+
break;
|
|
2720
|
+
}
|
|
2300
2721
|
parts.push(visit(statement));
|
|
2301
2722
|
if (isIfStatement(statement) && !statement.elseStatement && alwaysExits(statement.thenStatement)) {
|
|
2302
2723
|
for (const name of getNarrowedNames(statement.expression, false)) {
|
|
@@ -2311,6 +2732,39 @@ function visitBlockStatements(statements) {
|
|
|
2311
2732
|
narrowedVariables.delete(name);
|
|
2312
2733
|
return parts.join('\t');
|
|
2313
2734
|
}
|
|
2735
|
+
function isUsingDeclaration(statement) {
|
|
2736
|
+
const kind = statement.kind === 'VariableStatement' ? statement.declarationList?.declarationKind : undefined;
|
|
2737
|
+
return kind === 'using' || kind === 'await using';
|
|
2738
|
+
}
|
|
2739
|
+
// using x = r; …rest → x := r; defer dispose(x); …rest. In a function body Go's
|
|
2740
|
+
// defer is the block end; nested blocks run the rest in a closure so disposal
|
|
2741
|
+
// happens when the block ends (return/break/continue there keep the defer form)
|
|
2742
|
+
function visitUsingDeclaration(statement, rest) {
|
|
2743
|
+
const isAwait = statement.declarationList.declarationKind === 'await using';
|
|
2744
|
+
const disposals = (statement.declarationList.declarations ?? [])
|
|
2745
|
+
.filter((d) => isIdentifier(d.name))
|
|
2746
|
+
.map((d) => {
|
|
2747
|
+
const name = visit(d.name);
|
|
2748
|
+
const goType = inferExpressionType(d.initializer) ?? '';
|
|
2749
|
+
const className = goType.replace(/^\*/, '').replace(/\[.*$/, '');
|
|
2750
|
+
const classNode = declaredClasses.get(className);
|
|
2751
|
+
const hasAsync = (classNode?.members ?? []).some((m) => m.name?.text === '__asyncDispose');
|
|
2752
|
+
if (isAwait && hasAsync)
|
|
2753
|
+
useHelper('promise');
|
|
2754
|
+
const call = isAwait && hasAsync ? `TnAwait(${name}.__asyncDispose())` : `${name}.__dispose()`;
|
|
2755
|
+
return `defer func() { if ${name} != nil { ${call} } }()`;
|
|
2756
|
+
})
|
|
2757
|
+
.reverse();
|
|
2758
|
+
const declaration = { ...statement, declarationList: { ...statement.declarationList, declarationKind: 'const' } };
|
|
2759
|
+
const head = `${visit(declaration)}${disposals.join('\n\t\t')}\n\t\t`;
|
|
2760
|
+
const block = statement.parent;
|
|
2761
|
+
const isFunctionBody = !!block?.parent && block.parent.body === block;
|
|
2762
|
+
const restBlock = { kind: 'Block', statements: rest };
|
|
2763
|
+
const exits = containsNode(restBlock, (n) => n.kind === 'ReturnStatement' || n.kind === 'BreakStatement' || n.kind === 'ContinueStatement');
|
|
2764
|
+
if (isFunctionBody || exits)
|
|
2765
|
+
return head + visitBlockStatements(rest);
|
|
2766
|
+
return `func() {\n\t\t${head}${visitBlockStatements(rest)}}()\n\t`;
|
|
2767
|
+
}
|
|
2314
2768
|
function visitDynamicArithmetic(node, op) {
|
|
2315
2769
|
const leftDynamic = isDynamicValue(node.left);
|
|
2316
2770
|
const rightDynamic = isDynamicValue(node.right);
|
|
@@ -2515,6 +2969,66 @@ function visitSpreadCallArguments(node) {
|
|
|
2515
2969
|
return result;
|
|
2516
2970
|
}
|
|
2517
2971
|
// self.field = initializer for each instance field that has one
|
|
2972
|
+
// Whether a class's constructor chain (its own or an ancestor's) reads new.target
|
|
2973
|
+
function usesNewTarget(className) {
|
|
2974
|
+
let current = className;
|
|
2975
|
+
for (let depth = 0; depth < 50; depth++) {
|
|
2976
|
+
const classNode = declaredClasses.get(current);
|
|
2977
|
+
if (!classNode)
|
|
2978
|
+
return false;
|
|
2979
|
+
const ctor = (classNode.members ?? []).find((m) => isConstructorDeclaration(m));
|
|
2980
|
+
if (ctor?.body && containsNode(ctor.body, (n) => isNewTargetNode(n)))
|
|
2981
|
+
return true;
|
|
2982
|
+
const extendsClause = (classNode.heritageClauses ?? []).find((c) => c.token === 'ExtendsKeyword');
|
|
2983
|
+
if (!extendsClause)
|
|
2984
|
+
return false;
|
|
2985
|
+
current = visit(extendsClause.types[0].expression);
|
|
2986
|
+
}
|
|
2987
|
+
return false;
|
|
2988
|
+
}
|
|
2989
|
+
function isNewTargetNode(node) {
|
|
2990
|
+
return node?.kind === 'MetaProperty' && node.name?.text === 'target';
|
|
2991
|
+
}
|
|
2992
|
+
// The parent part of a subclass constructor: new.target-aware chains pass the target on
|
|
2993
|
+
function parentConstructorCall(parentClass, args) {
|
|
2994
|
+
if (!usesNewTarget(parentClass))
|
|
2995
|
+
return `New${parentClass}(${args.join(', ')})`;
|
|
2996
|
+
return `new${parentClass}As(${['__newTarget', ...args].join(', ')})`;
|
|
2997
|
+
}
|
|
2998
|
+
// Splits a Go parameter list at top-level commas
|
|
2999
|
+
function splitGoParameters(signature) {
|
|
3000
|
+
const parts = [];
|
|
3001
|
+
let depth = 0;
|
|
3002
|
+
let current = '';
|
|
3003
|
+
for (const ch of signature) {
|
|
3004
|
+
if (ch === '(' || ch === '[' || ch === '{')
|
|
3005
|
+
depth++;
|
|
3006
|
+
if (ch === ')' || ch === ']' || ch === '}')
|
|
3007
|
+
depth--;
|
|
3008
|
+
if (ch === ',' && depth === 0) {
|
|
3009
|
+
parts.push(current.trim());
|
|
3010
|
+
current = '';
|
|
3011
|
+
}
|
|
3012
|
+
else
|
|
3013
|
+
current += ch;
|
|
3014
|
+
}
|
|
3015
|
+
if (current.trim())
|
|
3016
|
+
parts.push(current.trim());
|
|
3017
|
+
return parts;
|
|
3018
|
+
}
|
|
3019
|
+
// NewX(…) — or, for new.target-aware classes, newXAs(__newTarget string, …) plus a
|
|
3020
|
+
// NewX(…) that calls it with "X"
|
|
3021
|
+
function emitConstructor(name, typeParams, typeParamNames, signature, body) {
|
|
3022
|
+
if (!usesNewTarget(name))
|
|
3023
|
+
return `func New${name}${typeParams}(${signature}) *${name}${typeParamNames} ${body}\n\n`;
|
|
3024
|
+
const forward = splitGoParameters(signature).map((p) => {
|
|
3025
|
+
const [paramName, paramType] = [p.slice(0, p.indexOf(' ')), p.slice(p.indexOf(' ') + 1)];
|
|
3026
|
+
return paramType.startsWith('...') ? `${paramName}...` : paramName;
|
|
3027
|
+
});
|
|
3028
|
+
const internal = ['__newTarget string', signature].filter((part) => part).join(', ');
|
|
3029
|
+
return (`func new${name}As${typeParams}(${internal}) *${name}${typeParamNames} ${body}\n\n` +
|
|
3030
|
+
`func New${name}${typeParams}(${signature}) *${name}${typeParamNames} {\n\t\treturn new${name}As${typeParamNames}(${[toGoStringLiteral(name), ...forward].join(', ')})\n\t}\n\n`);
|
|
3031
|
+
}
|
|
2518
3032
|
function fieldInitializers(classNode) {
|
|
2519
3033
|
return (classNode.members ?? [])
|
|
2520
3034
|
.filter((m) => isPropertyDeclaration(m) &&
|
|
@@ -2533,6 +3047,71 @@ function getPropertyDeclarationType(member) {
|
|
|
2533
3047
|
return getOptionalNodeType(member.type, !!member.questionToken);
|
|
2534
3048
|
return toFieldGoType(inferExpressionType(member.initializer));
|
|
2535
3049
|
}
|
|
3050
|
+
// Sorts in place and returns the array; without a comparator, compares as strings (like JS)
|
|
3051
|
+
function sortArray(obj, args) {
|
|
3052
|
+
importedPackages.add('sort');
|
|
3053
|
+
const arrayType = currentReceiverGoType?.startsWith('[]') ? currentReceiverGoType : '[]interface{}';
|
|
3054
|
+
let less = `(${args[0]})(__s[i], __s[j]) < 0`;
|
|
3055
|
+
if (!args[0]) {
|
|
3056
|
+
importedPackages.add('fmt');
|
|
3057
|
+
less = `fmt.Sprintf("%v", __s[i]) < fmt.Sprintf("%v", __s[j])`;
|
|
3058
|
+
}
|
|
3059
|
+
return `func() ${arrayType} { __s := ${obj}; sort.SliceStable(__s, func(i, j int) bool { return ${less} }); return __s }()`;
|
|
3060
|
+
}
|
|
3061
|
+
function mathCall(fn, args) {
|
|
3062
|
+
importedPackages.add('math');
|
|
3063
|
+
return `math.${fn}(${args.join(', ')})`;
|
|
3064
|
+
}
|
|
3065
|
+
// Go types of the arguments of the call being emitted (for call handlers)
|
|
3066
|
+
let currentCallArgTypes = [];
|
|
3067
|
+
// for headers with several declarations / updates: Go's parallel forms
|
|
3068
|
+
// (i, j := 0, 10 and i, j = i+1, j-1)
|
|
3069
|
+
function visitMultiForHeader(node) {
|
|
3070
|
+
const declarations = isVariableDeclarationList(node.initializer) ? (node.initializer.declarations ?? []) : [];
|
|
3071
|
+
const updates = [];
|
|
3072
|
+
const collect = (expr) => {
|
|
3073
|
+
if (!expr)
|
|
3074
|
+
return;
|
|
3075
|
+
if (isBinaryExpression(expr) && expr.operatorToken.kind === 'CommaToken') {
|
|
3076
|
+
collect(expr.left);
|
|
3077
|
+
collect(expr.right);
|
|
3078
|
+
}
|
|
3079
|
+
else
|
|
3080
|
+
updates.push(expr);
|
|
3081
|
+
};
|
|
3082
|
+
collect(node.incrementor);
|
|
3083
|
+
if (declarations.length < 2 && updates.length < 2)
|
|
3084
|
+
return undefined;
|
|
3085
|
+
let init = visit(node.initializer, { inline: true });
|
|
3086
|
+
if (declarations.length >= 2) {
|
|
3087
|
+
for (const d of declarations) {
|
|
3088
|
+
if (isIdentifier(d.name) && d.initializer)
|
|
3089
|
+
registerLocalVariable(d.name.text, inferExpressionType(d.initializer));
|
|
3090
|
+
}
|
|
3091
|
+
init = `${declarations.map((d) => visit(d.name)).join(', ')} := ${declarations.map((d) => (d.initializer ? visit(d.initializer) : 'nil')).join(', ')}`;
|
|
3092
|
+
}
|
|
3093
|
+
let post = visit(node.incrementor, { inline: true });
|
|
3094
|
+
if (updates.length >= 2) {
|
|
3095
|
+
const targets = [];
|
|
3096
|
+
const values = [];
|
|
3097
|
+
for (const u of updates) {
|
|
3098
|
+
if ((isPrefixUnaryExpression(u) || isPostfixUnaryExpression(u)) && isIncrementOrDecrement(u)) {
|
|
3099
|
+
const target = visit(u.operand);
|
|
3100
|
+
targets.push(target);
|
|
3101
|
+
values.push(`${target} ${u.operator === 'PlusPlusToken' ? '+' : '-'} 1`);
|
|
3102
|
+
}
|
|
3103
|
+
else if (isBinaryExpression(u) && u.operatorToken.kind === 'EqualsToken') {
|
|
3104
|
+
targets.push(visit(u.left));
|
|
3105
|
+
values.push(visit(u.right));
|
|
3106
|
+
}
|
|
3107
|
+
else
|
|
3108
|
+
return undefined;
|
|
3109
|
+
}
|
|
3110
|
+
post = `${targets.join(', ')} = ${values.join(', ')}`;
|
|
3111
|
+
}
|
|
3112
|
+
const condition = node.condition ? toGoCondition(node.condition) : '';
|
|
3113
|
+
return `for ${init}; ${condition}; ${post}${visitLoopBody(node.statement)}`;
|
|
3114
|
+
}
|
|
2536
3115
|
function isIncrementOrDecrement(node) {
|
|
2537
3116
|
return node.operator === 'PlusPlusToken' || node.operator === 'MinusMinusToken';
|
|
2538
3117
|
}
|
|
@@ -2598,6 +3177,15 @@ function getDictionaryValueType(node) {
|
|
|
2598
3177
|
function isDynamicObjectLiteral(node) {
|
|
2599
3178
|
if (isAnyContext(getContextualTypeNode(node)))
|
|
2600
3179
|
return true;
|
|
3180
|
+
// { name: { … } } nested in a dynamic object (with no declared member type) is dynamic too
|
|
3181
|
+
const parent = node.parent;
|
|
3182
|
+
if (isPropertyAssignment(parent) &&
|
|
3183
|
+
parent.initializer === node &&
|
|
3184
|
+
isObjectLiteralExpression(parent.parent) &&
|
|
3185
|
+
!getContextualTypeNode(node) &&
|
|
3186
|
+
isDynamicObjectLiteral(parent.parent)) {
|
|
3187
|
+
return true;
|
|
3188
|
+
}
|
|
2601
3189
|
return (node.properties ?? []).some((p) => p.kind === 'SpreadAssignment' && isDynamicValue(p.expression));
|
|
2602
3190
|
}
|
|
2603
3191
|
function getObjectLiteralGoType(node) {
|
|
@@ -2638,6 +3226,14 @@ function getObjectLiteralGoType(node) {
|
|
|
2638
3226
|
function isDynamicValue(expr) {
|
|
2639
3227
|
return inferExpressionType(expr) === 'interface{}';
|
|
2640
3228
|
}
|
|
3229
|
+
// A value whose type is a type parameter (a: T in f<T>): its properties are read dynamically
|
|
3230
|
+
function isTypeParameterValue(expr) {
|
|
3231
|
+
const type = inferExpressionType(expr);
|
|
3232
|
+
return !!type && isUnboundGoType(type) && isTypeParameterInScope(type, expr);
|
|
3233
|
+
}
|
|
3234
|
+
function hasDynamicProperties(expr) {
|
|
3235
|
+
return isDynamicValue(expr) || isTypeParameterValue(expr);
|
|
3236
|
+
}
|
|
2641
3237
|
// Whether an expression is written to (x = …, x += …, x++)
|
|
2642
3238
|
function isAssignmentTarget(node) {
|
|
2643
3239
|
const parent = node.parent;
|
|
@@ -2680,6 +3276,30 @@ function isAnyContext(typeNode) {
|
|
|
2680
3276
|
}
|
|
2681
3277
|
return false;
|
|
2682
3278
|
}
|
|
3279
|
+
const JS_MAP_METHODS = new Set(['get', 'set', 'has', 'delete', 'clear', 'keys', 'values', 'entries', 'forEach']);
|
|
3280
|
+
// rec.fn(args) where rec is a Record/dictionary (an ordered map) holding functions.
|
|
3281
|
+
// Names of JS Map methods (get, set, …) are Map methods unless rec is typed as a Record.
|
|
3282
|
+
function getRecordFunctionCall(node) {
|
|
3283
|
+
if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression))
|
|
3284
|
+
return undefined;
|
|
3285
|
+
const receiver = node.expression.expression;
|
|
3286
|
+
const mapType = inferExpressionType(receiver);
|
|
3287
|
+
if (!mapType?.startsWith('*TnMap['))
|
|
3288
|
+
return undefined;
|
|
3289
|
+
if (!extractMapValueType(mapType).startsWith('func('))
|
|
3290
|
+
return undefined;
|
|
3291
|
+
if (JS_MAP_METHODS.has(node.expression.name.text) && !isRecordTypeNode(resolveTypeNode(getExpressionTypeNode(receiver)))) {
|
|
3292
|
+
return undefined;
|
|
3293
|
+
}
|
|
3294
|
+
return { receiver, mapType };
|
|
3295
|
+
}
|
|
3296
|
+
function visitRecordFunctionCall(node) {
|
|
3297
|
+
const call = getRecordFunctionCall(node);
|
|
3298
|
+
if (!call)
|
|
3299
|
+
return undefined;
|
|
3300
|
+
const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
|
|
3301
|
+
return `${visit(call.receiver)}.Get(${toGoStringLiteral(node.expression.name.text)})(${args})`;
|
|
3302
|
+
}
|
|
2683
3303
|
function isRecordTypeNode(typeNode) {
|
|
2684
3304
|
return (isTypeReferenceNode(typeNode) &&
|
|
2685
3305
|
isIdentifier(typeNode.typeName) &&
|
|
@@ -2981,8 +3601,9 @@ function getContextualTypeNode(node) {
|
|
|
2981
3601
|
return getExpressionTypeNode(parent.left);
|
|
2982
3602
|
}
|
|
2983
3603
|
}
|
|
2984
|
-
if (isAsExpression(parent) || isTypeAssertionExpression(parent))
|
|
3604
|
+
if (isAsExpression(parent) || isTypeAssertionExpression(parent) || parent.kind === 'SatisfiesExpression') {
|
|
2985
3605
|
return parent.type;
|
|
3606
|
+
}
|
|
2986
3607
|
if ((isVariableDeclaration(parent) || isPropertyDeclaration(parent) || parent.kind === 'Parameter') &&
|
|
2987
3608
|
parent.initializer === node) {
|
|
2988
3609
|
return parent.type;
|
|
@@ -3012,6 +3633,14 @@ function getContextualTypeNode(node) {
|
|
|
3012
3633
|
}
|
|
3013
3634
|
return undefined;
|
|
3014
3635
|
}
|
|
3636
|
+
// A type parameter of an enclosing function or class (T inside function f<T>)
|
|
3637
|
+
function isTypeParameterInScope(name, node) {
|
|
3638
|
+
for (let n = node; n; n = n.parent) {
|
|
3639
|
+
if ((n.typeParameters ?? []).some((tp) => tp.name?.text === name))
|
|
3640
|
+
return true;
|
|
3641
|
+
}
|
|
3642
|
+
return false;
|
|
3643
|
+
}
|
|
3015
3644
|
// Element type of an array literal: from its contextual type (declared
|
|
3016
3645
|
// variable/parameter/return type), else inferred from its first typed element
|
|
3017
3646
|
function getArrayLiteralElementType(node) {
|
|
@@ -3019,7 +3648,7 @@ function getArrayLiteralElementType(node) {
|
|
|
3019
3648
|
if (isArrayTypeNode(contextual)) {
|
|
3020
3649
|
const elem = getType(contextual.elementType);
|
|
3021
3650
|
// An unbound type parameter (call-site []T) resolves from the elements
|
|
3022
|
-
if (!isUnboundGoType(elem))
|
|
3651
|
+
if (!isUnboundGoType(elem) || isTypeParameterInScope(elem, node))
|
|
3023
3652
|
return elem;
|
|
3024
3653
|
}
|
|
3025
3654
|
if (contextual?.kind === 'TupleType')
|
|
@@ -3032,6 +3661,7 @@ function getArrayLiteralElementType(node) {
|
|
|
3032
3661
|
if (!isUnboundGoType(elem))
|
|
3033
3662
|
return elem;
|
|
3034
3663
|
}
|
|
3664
|
+
let firstElementType;
|
|
3035
3665
|
for (const element of node.elements ?? []) {
|
|
3036
3666
|
if (isSpreadElement(element)) {
|
|
3037
3667
|
const spreadType = inferExpressionType(element.expression);
|
|
@@ -3044,11 +3674,16 @@ function getArrayLiteralElementType(node) {
|
|
|
3044
3674
|
}
|
|
3045
3675
|
continue;
|
|
3046
3676
|
}
|
|
3677
|
+
// mixed element types ([['a', 1]]) make an interface{} array
|
|
3047
3678
|
const elementType = inferExpressionType(element);
|
|
3048
|
-
if (elementType && elementType !== 'nil' && !isUnboundGoType(elementType))
|
|
3049
|
-
|
|
3679
|
+
if (elementType && elementType !== 'nil' && !isUnboundGoType(elementType)) {
|
|
3680
|
+
if (firstElementType && firstElementType !== elementType)
|
|
3681
|
+
return 'interface{}';
|
|
3682
|
+
if (!firstElementType)
|
|
3683
|
+
firstElementType = elementType;
|
|
3684
|
+
}
|
|
3050
3685
|
}
|
|
3051
|
-
return 'interface{}';
|
|
3686
|
+
return firstElementType ?? 'interface{}';
|
|
3052
3687
|
}
|
|
3053
3688
|
// A Go type that is a generic type parameter of the enclosing function (T)
|
|
3054
3689
|
// rather than a concrete type: unresolvable at the call site
|
|
@@ -3111,6 +3746,13 @@ function getTupleElementType(tuple) {
|
|
|
3111
3746
|
function getTypeText(typeNode) {
|
|
3112
3747
|
if (!typeNode)
|
|
3113
3748
|
return ':';
|
|
3749
|
+
// Partial<T> etc. construct T
|
|
3750
|
+
if (isTypeReferenceNode(typeNode) &&
|
|
3751
|
+
isIdentifier(typeNode.typeName) &&
|
|
3752
|
+
UTILITY_TYPES.has(typeNode.typeName.text) &&
|
|
3753
|
+
typeNode.typeArguments?.length) {
|
|
3754
|
+
return getTypeText(typeNode.typeArguments[0]);
|
|
3755
|
+
}
|
|
3114
3756
|
if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
|
|
3115
3757
|
return typeNode.typeName.text;
|
|
3116
3758
|
}
|
|
@@ -3187,8 +3829,10 @@ function visitTemplateExpression(node) {
|
|
|
3187
3829
|
function isVoidStatementCall(expr) {
|
|
3188
3830
|
if (!isCallExpression(expr))
|
|
3189
3831
|
return false;
|
|
3190
|
-
if (isIdentifier(expr.expression))
|
|
3191
|
-
|
|
3832
|
+
if (isIdentifier(expr.expression)) {
|
|
3833
|
+
const name = expr.expression.text;
|
|
3834
|
+
return name === 'assert' || (!!promiseResolveName && name === promiseResolveName) || (!!promiseRejectName && name === promiseRejectName);
|
|
3835
|
+
}
|
|
3192
3836
|
return (isPropertyAccessExpression(expr.expression) &&
|
|
3193
3837
|
isIdentifier(expr.expression.expression) &&
|
|
3194
3838
|
expr.expression.expression.text === 'console');
|
|
@@ -3298,10 +3942,18 @@ function inferExpressionType(expr) {
|
|
|
3298
3942
|
}
|
|
3299
3943
|
if (isNumericLiteral(expr))
|
|
3300
3944
|
return 'float64';
|
|
3945
|
+
if (expr.kind === 'VoidExpression')
|
|
3946
|
+
return 'nil';
|
|
3947
|
+
if (expr.kind === 'SatisfiesExpression')
|
|
3948
|
+
return inferExpressionType(expr.expression);
|
|
3301
3949
|
if (expr.kind === 'BigIntLiteral')
|
|
3302
3950
|
return '*tnbig.Int';
|
|
3303
3951
|
if (expr.kind === 'TypeOfExpression')
|
|
3304
3952
|
return 'string';
|
|
3953
|
+
if (isJsxNode(expr))
|
|
3954
|
+
return 'TnJsxNode';
|
|
3955
|
+
if (expr.kind === 'TaggedTemplateExpression' && isStringRawTag(expr.tag))
|
|
3956
|
+
return 'string';
|
|
3305
3957
|
if (isRegularExpressionLiteral(expr)) {
|
|
3306
3958
|
const lastSlash = expr.text.lastIndexOf('/');
|
|
3307
3959
|
if (needsStatefulRegex(expr.text.substring(1, lastSlash), expr.text.substring(lastSlash + 1))) {
|
|
@@ -3375,6 +4027,8 @@ function inferExpressionType(expr) {
|
|
|
3375
4027
|
return '[]string';
|
|
3376
4028
|
}
|
|
3377
4029
|
// import.meta.url is a string
|
|
4030
|
+
if (isPropertyAccessExpression(expr) && isNewTargetNode(expr.expression) && expr.name.text === 'name')
|
|
4031
|
+
return 'string';
|
|
3378
4032
|
if (isPropertyAccessExpression(expr) && expr.expression.kind === 'MetaProperty' && expr.name.text === 'url') {
|
|
3379
4033
|
return 'string';
|
|
3380
4034
|
}
|
|
@@ -3396,6 +4050,15 @@ function inferExpressionType(expr) {
|
|
|
3396
4050
|
: isPropertyAccessExpression(expr.expression) && isIdentifier(expr.expression.expression)
|
|
3397
4051
|
? `${expr.expression.expression.text}.${expr.expression.name.text}`
|
|
3398
4052
|
: undefined;
|
|
4053
|
+
const promiseType = inferPromiseCallType(expr);
|
|
4054
|
+
if (promiseType)
|
|
4055
|
+
return promiseType;
|
|
4056
|
+
const objectCallType = inferObjectCallType(expr);
|
|
4057
|
+
if (objectCallType)
|
|
4058
|
+
return objectCallType;
|
|
4059
|
+
if (isIdentifier(expr.expression) && expr.expression.text === 'structuredClone') {
|
|
4060
|
+
return inferExpressionType(expr.arguments?.[0]);
|
|
4061
|
+
}
|
|
3399
4062
|
// Promise.all([...]) resolves to a slice over a channel
|
|
3400
4063
|
if (isPropertyAccessExpression(expr.expression) &&
|
|
3401
4064
|
isIdentifier(expr.expression.expression) &&
|
|
@@ -3441,12 +4104,21 @@ function inferExpressionType(expr) {
|
|
|
3441
4104
|
if (builtinType && !declaredFunctions.has(expr.expression.text))
|
|
3442
4105
|
return builtinType;
|
|
3443
4106
|
}
|
|
3444
|
-
|
|
4107
|
+
const regexPropertyType = inferRegexPropertyType(expr);
|
|
4108
|
+
if (regexPropertyType)
|
|
4109
|
+
return regexPropertyType;
|
|
4110
|
+
if (isPropertyAccessExpression(expr) && inferExpressionType(expr.expression) === '*TnDecoratorContext') {
|
|
4111
|
+
return ['static', 'private'].includes(expr.name.text) ? 'bool' : 'string';
|
|
4112
|
+
}
|
|
4113
|
+
if (isPropertyAccessExpression(expr) && expr.name.text === 'length' && hasDynamicProperties(expr.expression)) {
|
|
3445
4114
|
return 'float64';
|
|
3446
4115
|
}
|
|
3447
4116
|
if ((isPropertyAccessExpression(expr) || isElementAccessExpression(expr)) && isDynamicValue(expr.expression)) {
|
|
3448
4117
|
return 'interface{}';
|
|
3449
4118
|
}
|
|
4119
|
+
if (isPropertyAccessExpression(expr) && !isCallee(expr) && isTypeParameterValue(expr.expression)) {
|
|
4120
|
+
return 'interface{}';
|
|
4121
|
+
}
|
|
3450
4122
|
if (isCallExpression(expr) &&
|
|
3451
4123
|
isPropertyAccessExpression(expr.expression) &&
|
|
3452
4124
|
isIdentifier(expr.expression.expression) &&
|
|
@@ -3456,6 +4128,15 @@ function inferExpressionType(expr) {
|
|
|
3456
4128
|
}
|
|
3457
4129
|
if (isCallExpression(expr) && isObjectKeysOfDynamic(expr))
|
|
3458
4130
|
return '[]string';
|
|
4131
|
+
// f?.() is undefined when f is
|
|
4132
|
+
if (isCallExpression(expr) && hasQuestionDot(expr)) {
|
|
4133
|
+
const fnType = inferExpressionType(expr.expression) ?? '';
|
|
4134
|
+
const returnType = fnType.startsWith('func(') ? goFuncReturnType(fnType) : undefined;
|
|
4135
|
+
return returnType ? makeNullableType(returnType) : undefined;
|
|
4136
|
+
}
|
|
4137
|
+
const recordCall = isCallExpression(expr) ? getRecordFunctionCall(expr) : undefined;
|
|
4138
|
+
if (recordCall)
|
|
4139
|
+
return goFuncReturnType(extractMapValueType(recordCall.mapType));
|
|
3459
4140
|
if (isCallExpression(expr) &&
|
|
3460
4141
|
isPropertyAccessExpression(expr.expression) &&
|
|
3461
4142
|
isIdentifier(expr.expression.expression) &&
|
|
@@ -3491,6 +4172,9 @@ function inferExpressionType(expr) {
|
|
|
3491
4172
|
// Numeric enums are float64 constants; string enums keep their type
|
|
3492
4173
|
if (enumBaseTypes.get(expr.expression.text) === 'float64')
|
|
3493
4174
|
return 'float64';
|
|
4175
|
+
if (enumBaseTypes.get(expr.expression.text) === 'interface{}') {
|
|
4176
|
+
return enumMemberGoTypes.get(`${expr.expression.text}.${getEnumMemberName(expr.name)}`) ?? 'interface{}';
|
|
4177
|
+
}
|
|
3494
4178
|
const enumType = getSafeName(expr.expression.text);
|
|
3495
4179
|
return enumType;
|
|
3496
4180
|
}
|
|
@@ -3570,15 +4254,17 @@ function inferExpressionType(expr) {
|
|
|
3570
4254
|
if (methodName === 'getTime' || methodName === 'valueOf' || methodName === 'getMilliseconds' ||
|
|
3571
4255
|
methodName === 'getSeconds' || methodName === 'getMinutes' || methodName === 'getHours' ||
|
|
3572
4256
|
methodName === 'getDate' || methodName === 'getDay' || methodName === 'getMonth' ||
|
|
3573
|
-
methodName === 'getFullYear')
|
|
4257
|
+
methodName === 'getFullYear' || methodName.startsWith('getUTC'))
|
|
3574
4258
|
return 'float64';
|
|
3575
4259
|
if (methodName === 'toISOString' || methodName === 'toJSON' || methodName === 'toString' ||
|
|
3576
4260
|
methodName === 'toDateString' || methodName === 'toTimeString')
|
|
3577
4261
|
return 'string';
|
|
3578
4262
|
}
|
|
3579
4263
|
if (ownerType && ownerType.startsWith('*TnMap[')) {
|
|
3580
|
-
if (
|
|
4264
|
+
if (['has', 'isSubsetOf', 'isSupersetOf', 'isDisjointFrom'].includes(methodName))
|
|
3581
4265
|
return 'bool';
|
|
4266
|
+
if (['union', 'intersection', 'difference', 'symmetricDifference'].includes(methodName))
|
|
4267
|
+
return ownerType;
|
|
3582
4268
|
if (methodName === 'get') {
|
|
3583
4269
|
const valueType = extractMapValueType(ownerType);
|
|
3584
4270
|
return isStructGoType(valueType) ? `*${valueType}` : valueType;
|
|
@@ -3602,6 +4288,10 @@ function inferExpressionType(expr) {
|
|
|
3602
4288
|
return 'float64';
|
|
3603
4289
|
if (['pop', 'shift', 'at'].includes(methodName))
|
|
3604
4290
|
return elementType;
|
|
4291
|
+
if (['concat', 'toSorted', 'toReversed', 'with', 'copyWithin', 'toSpliced'].includes(methodName))
|
|
4292
|
+
return ownerType;
|
|
4293
|
+
if (methodName === 'reduceRight' && expr.arguments?.[1])
|
|
4294
|
+
return inferExpressionType(expr.arguments[1]);
|
|
3605
4295
|
if (methodName === 'flat' && elementType.startsWith('[]'))
|
|
3606
4296
|
return elementType;
|
|
3607
4297
|
if (methodName === 'flat' || methodName === 'flatMap')
|
|
@@ -3698,6 +4388,11 @@ function inferExpressionType(expr) {
|
|
|
3698
4388
|
return undefined;
|
|
3699
4389
|
}
|
|
3700
4390
|
const BUILTIN_CALL_TYPES = {
|
|
4391
|
+
isFinite: 'bool',
|
|
4392
|
+
encodeURIComponent: 'string',
|
|
4393
|
+
decodeURIComponent: 'string',
|
|
4394
|
+
'Object.fromEntries': 'interface{}',
|
|
4395
|
+
'Date.UTC': 'float64',
|
|
3701
4396
|
'Number.isNaN': 'bool',
|
|
3702
4397
|
'Number.isFinite': 'bool',
|
|
3703
4398
|
'Number.isInteger': 'bool',
|
|
@@ -3705,6 +4400,156 @@ const BUILTIN_CALL_TYPES = {
|
|
|
3705
4400
|
'Object.assign': '*TnMap[string, interface{}]',
|
|
3706
4401
|
'Date.now': 'float64'
|
|
3707
4402
|
};
|
|
4403
|
+
// Object.freeze & co. record the integrity level (1 preventExtensions, 2 seal,
|
|
4404
|
+
// 3 freeze) by identity; TypeScript's Readonly<T> result types reject writes
|
|
4405
|
+
const OBJECT_INTEGRITY_LEVELS = { preventExtensions: 1, seal: 2, freeze: 3 };
|
|
4406
|
+
const OBJECT_INTEGRITY_CHECKS = { isExtensible: 1, isSealed: 2, isFrozen: 3 };
|
|
4407
|
+
function getObjectCallName(node) {
|
|
4408
|
+
if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression))
|
|
4409
|
+
return undefined;
|
|
4410
|
+
if (!isIdentifier(node.expression.expression) || node.expression.expression.text !== 'Object')
|
|
4411
|
+
return undefined;
|
|
4412
|
+
return node.expression.name.text;
|
|
4413
|
+
}
|
|
4414
|
+
// The Go type of a property of an object type (anonymous struct, interface or class)
|
|
4415
|
+
function getObjectFieldGoType(objectType, field) {
|
|
4416
|
+
const base = objectType.replace(/^\*/, '').replace(/\[.*$/, '');
|
|
4417
|
+
return (getStructFieldGoType(objectType, field) ??
|
|
4418
|
+
interfacePropertyTypes.get(base)?.get(field) ??
|
|
4419
|
+
classPropertyTypes.get(base)?.get(field));
|
|
4420
|
+
}
|
|
4421
|
+
function inferObjectCallType(expr) {
|
|
4422
|
+
const name = getObjectCallName(expr);
|
|
4423
|
+
if (!name)
|
|
4424
|
+
return undefined;
|
|
4425
|
+
if (OBJECT_INTEGRITY_LEVELS[name] || name === 'defineProperty')
|
|
4426
|
+
return inferExpressionType(expr.arguments?.[0]);
|
|
4427
|
+
if (OBJECT_INTEGRITY_CHECKS[name] || name === 'hasOwn')
|
|
4428
|
+
return 'bool';
|
|
4429
|
+
if (name === 'getOwnPropertyNames')
|
|
4430
|
+
return inferExpressionType({ ...expr, expression: { ...expr.expression, name: { kind: 'Identifier', text: 'keys' } } });
|
|
4431
|
+
return undefined;
|
|
4432
|
+
}
|
|
4433
|
+
function visitObjectCall(node) {
|
|
4434
|
+
const name = getObjectCallName(node);
|
|
4435
|
+
if (!name)
|
|
4436
|
+
return undefined;
|
|
4437
|
+
const args = node.arguments ?? [];
|
|
4438
|
+
if (OBJECT_INTEGRITY_LEVELS[name]) {
|
|
4439
|
+
useHelper('objectIntegrity');
|
|
4440
|
+
return `TnSetIntegrity(${visit(args[0])}, ${OBJECT_INTEGRITY_LEVELS[name]})`;
|
|
4441
|
+
}
|
|
4442
|
+
if (OBJECT_INTEGRITY_CHECKS[name]) {
|
|
4443
|
+
useHelper('objectIntegrity');
|
|
4444
|
+
const check = `TnHasIntegrity(${visit(args[0])}, ${OBJECT_INTEGRITY_CHECKS[name]})`;
|
|
4445
|
+
return name === 'isExtensible' ? `!${check}` : check;
|
|
4446
|
+
}
|
|
4447
|
+
if (name === 'getOwnPropertyNames') {
|
|
4448
|
+
return visit({ ...node, expression: { ...node.expression, name: { kind: 'Identifier', text: 'keys' } } });
|
|
4449
|
+
}
|
|
4450
|
+
const objectType = inferExpressionType(args[0]) ?? '';
|
|
4451
|
+
const key = args[1];
|
|
4452
|
+
if (name === 'hasOwn') {
|
|
4453
|
+
if (isDynamicValue(args[0])) {
|
|
4454
|
+
useHelper('dynamic');
|
|
4455
|
+
return `TnHas(${visit(args[0])}, ${visit(key)})`;
|
|
4456
|
+
}
|
|
4457
|
+
if (objectType.startsWith('*TnMap['))
|
|
4458
|
+
return `${visit(args[0])}.Has(${visit(key)})`;
|
|
4459
|
+
if (!isStringLiteral(key))
|
|
4460
|
+
throw new Error('Object.hasOwn on a typed object needs a literal key');
|
|
4461
|
+
const fieldType = getObjectFieldGoType(objectType, key.text);
|
|
4462
|
+
if (!fieldType)
|
|
4463
|
+
return 'false';
|
|
4464
|
+
// an absent optional primitive is nil
|
|
4465
|
+
return /^\*(float64|string|bool)$/.test(fieldType) ? `(${visit(args[0])}.${goFieldName(key.text)} != nil)` : 'true';
|
|
4466
|
+
}
|
|
4467
|
+
if (name === 'defineProperty') {
|
|
4468
|
+
const descriptor = args[2];
|
|
4469
|
+
const valueProp = isObjectLiteralExpression(descriptor)
|
|
4470
|
+
? (descriptor.properties ?? []).find((p) => isPropertyAssignment(p) && isIdentifier(p.name) && p.name.text === 'value')
|
|
4471
|
+
: undefined;
|
|
4472
|
+
if (!valueProp)
|
|
4473
|
+
throw new Error('Object.defineProperty needs a { value } descriptor literal');
|
|
4474
|
+
const target = visit(args[0]);
|
|
4475
|
+
if (isDynamicValue(args[0])) {
|
|
4476
|
+
useHelper('dynamic');
|
|
4477
|
+
return `TnSet(${target}, ${visit(key)}, ${visit(valueProp.initializer)})`;
|
|
4478
|
+
}
|
|
4479
|
+
if (objectType.startsWith('*TnMap[')) {
|
|
4480
|
+
return `${target}.Set(${visit(key)}, ${toGoValueOfType(valueProp.initializer, extractMapValueType(objectType))})`;
|
|
4481
|
+
}
|
|
4482
|
+
if (!isStringLiteral(key))
|
|
4483
|
+
throw new Error('Object.defineProperty on a typed object needs a literal key');
|
|
4484
|
+
return `${target}.${goFieldName(key.text)} = ${toGoValueOfType(valueProp.initializer, getObjectFieldGoType(objectType, key.text))}`;
|
|
4485
|
+
}
|
|
4486
|
+
return undefined;
|
|
4487
|
+
}
|
|
4488
|
+
const PROMISE_COMBINATORS = new Set(['race', 'allSettled', 'any']);
|
|
4489
|
+
function isPromiseStaticCall(node, names) {
|
|
4490
|
+
return (isCallExpression(node) &&
|
|
4491
|
+
isPropertyAccessExpression(node.expression) &&
|
|
4492
|
+
isIdentifier(node.expression.expression) &&
|
|
4493
|
+
node.expression.expression.text === 'Promise' &&
|
|
4494
|
+
names.has(node.expression.name.text));
|
|
4495
|
+
}
|
|
4496
|
+
// p.catch(cb) / p.finally(cb) on a promise channel
|
|
4497
|
+
function isPromiseMethodCall(node) {
|
|
4498
|
+
return (isCallExpression(node) &&
|
|
4499
|
+
isPropertyAccessExpression(node.expression) &&
|
|
4500
|
+
['catch', 'finally'].includes(node.expression.name.text) &&
|
|
4501
|
+
!!inferExpressionType(node.expression.expression)?.startsWith('chan '));
|
|
4502
|
+
}
|
|
4503
|
+
// The value type of Promise.reject(…): from sibling promises in an array, else the
|
|
4504
|
+
// enclosing function's Promise<T>
|
|
4505
|
+
function getRejectedPromiseType(node) {
|
|
4506
|
+
const parent = node.parent;
|
|
4507
|
+
if (isArrayLiteralExpression(parent)) {
|
|
4508
|
+
for (const sibling of parent.elements ?? []) {
|
|
4509
|
+
if (sibling === node || isPromiseStaticCall(sibling, new Set(['reject'])))
|
|
4510
|
+
continue;
|
|
4511
|
+
const type = inferExpressionType(sibling);
|
|
4512
|
+
if (type?.startsWith('chan '))
|
|
4513
|
+
return type.slice(5);
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
return getPromiseChannelType(node);
|
|
4517
|
+
}
|
|
4518
|
+
function inferPromiseCallType(expr) {
|
|
4519
|
+
if (isPromiseStaticCall(expr, new Set(['reject'])))
|
|
4520
|
+
return `chan ${getRejectedPromiseType(expr)}`;
|
|
4521
|
+
if (isPromiseStaticCall(expr, PROMISE_COMBINATORS)) {
|
|
4522
|
+
const elem = promiseAllElementType(expr) ?? 'interface{}';
|
|
4523
|
+
return expr.expression.name.text === 'allSettled' ? `chan []*TnSettled[${elem}]` : `chan ${elem}`;
|
|
4524
|
+
}
|
|
4525
|
+
if (isPromiseMethodCall(expr))
|
|
4526
|
+
return inferExpressionType(expr.expression.expression);
|
|
4527
|
+
return undefined;
|
|
4528
|
+
}
|
|
4529
|
+
function visitPromiseCall(node) {
|
|
4530
|
+
const args = node.arguments ?? [];
|
|
4531
|
+
if (isPromiseStaticCall(node, new Set(['reject']))) {
|
|
4532
|
+
useHelper('promise');
|
|
4533
|
+
return `TnPromiseReject[${getRejectedPromiseType(node)}](${args[0] ? visit(args[0]) : 'nil'})`;
|
|
4534
|
+
}
|
|
4535
|
+
if (isPromiseStaticCall(node, PROMISE_COMBINATORS)) {
|
|
4536
|
+
useHelper('promise');
|
|
4537
|
+
const name = node.expression.name.text;
|
|
4538
|
+
return `TnPromise${name[0].toUpperCase()}${name.slice(1)}(${args[0] ? visit(args[0]) : '[]chan interface{}{}'})`;
|
|
4539
|
+
}
|
|
4540
|
+
if (!isPromiseMethodCall(node))
|
|
4541
|
+
return undefined;
|
|
4542
|
+
useHelper('promise');
|
|
4543
|
+
const receiver = visit(node.expression.expression);
|
|
4544
|
+
const callback = args[0];
|
|
4545
|
+
const cb = callback ? visit(callback) : 'func() {}';
|
|
4546
|
+
if (node.expression.name.text === 'finally')
|
|
4547
|
+
return `TnPromiseFinally(${receiver}, ${cb})`;
|
|
4548
|
+
// .catch(() => …) ignores the reason; .catch((e) => value) settles with the value
|
|
4549
|
+
const handler = callback && (callback.parameters ?? []).length === 0 ? `func(_ interface{}) { (${cb})() }` : cb;
|
|
4550
|
+
const returnsValue = callback && !/\)$/.test(inferExpressionType(callback) ?? ')');
|
|
4551
|
+
return `TnPromiseCatch${returnsValue ? 'Value' : ''}(${receiver}, ${handler})`;
|
|
4552
|
+
}
|
|
3708
4553
|
// Element type of `await Promise.all([...])` from the first promise channel
|
|
3709
4554
|
function promiseAllElementType(expr) {
|
|
3710
4555
|
const arrayNode = (expr.arguments ?? [])[0];
|
|
@@ -3716,11 +4561,14 @@ const BUILTIN_FUNCTION_TYPES = {
|
|
|
3716
4561
|
parseFloat: 'float64',
|
|
3717
4562
|
parseInt: 'float64',
|
|
3718
4563
|
Number: 'float64',
|
|
3719
|
-
String: 'string'
|
|
4564
|
+
String: 'string',
|
|
4565
|
+
setTimeout: '*time.Timer',
|
|
4566
|
+
setInterval: '*TnInterval'
|
|
3720
4567
|
};
|
|
3721
4568
|
const STRING_METHOD_RETURN_TYPES = {
|
|
3722
4569
|
trim: 'string',
|
|
3723
4570
|
trimStart: 'string',
|
|
4571
|
+
normalize: 'string',
|
|
3724
4572
|
trimEnd: 'string',
|
|
3725
4573
|
toUpperCase: 'string',
|
|
3726
4574
|
toLowerCase: 'string',
|
|
@@ -3875,6 +4723,46 @@ function makeNullableType(typeName) {
|
|
|
3875
4723
|
// property-only interfaces / object type aliases
|
|
3876
4724
|
// Object types that compile to Go structs: property-only interfaces and
|
|
3877
4725
|
// object type aliases (values of these types are *Name, like JS references)
|
|
4726
|
+
// A declared interface with method signatures (a Go interface type)
|
|
4727
|
+
function isMethodInterfaceName(name) {
|
|
4728
|
+
return (declaredInterfaces.get(name)?.members ?? []).some((m) => isMethodSignature(m));
|
|
4729
|
+
}
|
|
4730
|
+
// type P_obj struct { get_fn func() T; … } + func (o *P_obj) get() T { return o.get_fn() }
|
|
4731
|
+
function emitInterfaceAdapter(node) {
|
|
4732
|
+
const name = visit(node.name);
|
|
4733
|
+
const typeParams = getTypeParameters(node.typeParameters);
|
|
4734
|
+
const typeParamNames = getTypeParameterNames(node.typeParameters);
|
|
4735
|
+
const fields = [];
|
|
4736
|
+
const methods = [];
|
|
4737
|
+
for (const member of node.members ?? []) {
|
|
4738
|
+
if (!isMethodSignature(member))
|
|
4739
|
+
continue;
|
|
4740
|
+
const methodName = visit(member.name);
|
|
4741
|
+
const params = (member.parameters ?? []).map((p) => `${visit(p.name)} ${getType(p.type)}`);
|
|
4742
|
+
const args = (member.parameters ?? []).map((p) => visit(p.name));
|
|
4743
|
+
const returnType = member.type ? getType(member.type) : '';
|
|
4744
|
+
const signature = `func(${params.join(', ')})${returnType ? ` ${returnType}` : ''}`;
|
|
4745
|
+
fields.push(`\t${methodName}_fn ${signature}`);
|
|
4746
|
+
methods.push(`func (o *${name}_obj${typeParamNames}) ${methodName}(${params.join(', ')})${returnType ? ` ${returnType}` : ''} { ${returnType ? 'return ' : ''}o.${methodName}_fn(${args.join(', ')}) }`);
|
|
4747
|
+
}
|
|
4748
|
+
return `\n\ntype ${name}_obj${typeParams} struct {\n${fields.join('\n')}\n}\n\n${methods.join('\n\n')}`;
|
|
4749
|
+
}
|
|
4750
|
+
// { get: () => 4, set(v) { … } } typed as a method interface → &P_obj{get_fn: …, set_fn: …}
|
|
4751
|
+
function visitInterfaceLiteral(node, interfaceName, typeArgs) {
|
|
4752
|
+
const fields = (node.properties ?? []).map((p) => {
|
|
4753
|
+
const fieldName = `${visit(p.name)}_fn`;
|
|
4754
|
+
if (isPropertyAssignment(p))
|
|
4755
|
+
return `${fieldName}: ${visit(p.initializer)}`;
|
|
4756
|
+
if (isShorthandPropertyAssignment(p))
|
|
4757
|
+
return `${fieldName}: ${visit(p.name)}`;
|
|
4758
|
+
if (isMethodDeclaration(p)) {
|
|
4759
|
+
const fn = { kind: 'FunctionExpression', parameters: p.parameters, type: p.type, body: p.body, parent: p.parent };
|
|
4760
|
+
return `${fieldName}: ${visit(fn)}`;
|
|
4761
|
+
}
|
|
4762
|
+
return '';
|
|
4763
|
+
});
|
|
4764
|
+
return `&${interfaceName}_obj${typeArgs}${compositeBody(fields.filter((f) => f))}`;
|
|
4765
|
+
}
|
|
3878
4766
|
function isObjectTypeName(name) {
|
|
3879
4767
|
const alias = declaredTypeAliases.get(name) ?? typeAliases.get(name);
|
|
3880
4768
|
if (alias)
|
|
@@ -3886,6 +4774,43 @@ function isObjectTypeName(name) {
|
|
|
3886
4774
|
}
|
|
3887
4775
|
return interfacePropertyTypes.has(name);
|
|
3888
4776
|
}
|
|
4777
|
+
// The keys a mapped type iterates: string literals of a union, or keyof an object type
|
|
4778
|
+
function getMappedTypeKeys(constraint) {
|
|
4779
|
+
const keys = resolveTypeNode(constraint);
|
|
4780
|
+
if (!keys)
|
|
4781
|
+
return undefined;
|
|
4782
|
+
if (keys.kind === 'TypeOperator' && keys.operator === 'KeyOfKeyword') {
|
|
4783
|
+
const fields = getObjectTypeFields(keys.type);
|
|
4784
|
+
return fields.length > 0 ? fields.map((f) => f.split(' ')[0]) : undefined;
|
|
4785
|
+
}
|
|
4786
|
+
const types = isUnionTypeNode(keys) ? (keys.types ?? []) : [keys];
|
|
4787
|
+
if (!types.every((t) => isLiteralTypeNode(t) && isStringLiteral(t.literal)))
|
|
4788
|
+
return undefined;
|
|
4789
|
+
return types.map((t) => goFieldName(t.literal.text));
|
|
4790
|
+
}
|
|
4791
|
+
function getMappedType(typeNode) {
|
|
4792
|
+
const valueType = typeNode.type ? getOptionalNodeType(typeNode.type, !!typeNode.questionToken) : 'interface{}';
|
|
4793
|
+
const keys = getMappedTypeKeys(typeNode.typeParameter?.constraint);
|
|
4794
|
+
if (keys)
|
|
4795
|
+
return `*struct{ ${keys.map((k) => `${k} ${valueType}`).join('; ')} }`;
|
|
4796
|
+
return 'interface{}';
|
|
4797
|
+
}
|
|
4798
|
+
const UTILITY_TYPES = new Set(['Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'NonNullable']);
|
|
4799
|
+
// "name T" field declarations of an object type (type literal, alias or interface)
|
|
4800
|
+
function getObjectTypeFields(typeNode) {
|
|
4801
|
+
let members = [];
|
|
4802
|
+
const resolved = typeNode.kind === 'TypeLiteral' ? typeNode : undefined;
|
|
4803
|
+
if (resolved)
|
|
4804
|
+
members = resolved.members ?? [];
|
|
4805
|
+
else if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
|
|
4806
|
+
const name = typeNode.typeName.text;
|
|
4807
|
+
const alias = declaredTypeAliases.get(name) ?? typeAliases.get(name);
|
|
4808
|
+
members = alias?.kind === 'TypeLiteral' ? (alias.members ?? []) : (declaredInterfaces.get(name)?.members ?? []);
|
|
4809
|
+
}
|
|
4810
|
+
return members
|
|
4811
|
+
.filter((m) => isPropertySignature(m) && isIdentifier(m.name))
|
|
4812
|
+
.map((m) => `${goFieldName(m.name.text)} ${getOptionalNodeType(m.type, !!m.questionToken)}`);
|
|
4813
|
+
}
|
|
3889
4814
|
function isStructGoType(goType) {
|
|
3890
4815
|
return goType.startsWith('struct{') || interfacePropertyTypes.has(goType);
|
|
3891
4816
|
}
|
|
@@ -4071,6 +4996,18 @@ function withNarrowingType(names, infer) {
|
|
|
4071
4996
|
return type;
|
|
4072
4997
|
}
|
|
4073
4998
|
function visitOptionalCall(node) {
|
|
4999
|
+
// f?.(): call only when the function value is not nil
|
|
5000
|
+
if (hasQuestionDot(node)) {
|
|
5001
|
+
const fnType = inferExpressionType(node.expression) ?? '';
|
|
5002
|
+
const returnType = fnType.startsWith('func(') ? goFuncReturnType(fnType) : undefined;
|
|
5003
|
+
const fn = getTempName('fn');
|
|
5004
|
+
const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
|
|
5005
|
+
if (!returnType)
|
|
5006
|
+
return `func() { if ${fn} := ${visit(node.expression)}; ${fn} != nil { ${fn}(${args}) } }()`;
|
|
5007
|
+
const resultType = makeNullableType(returnType);
|
|
5008
|
+
const value = resultType === returnType ? `${fn}(${args})` : `func() ${resultType} { v := ${fn}(${args}); return &v }()`;
|
|
5009
|
+
return `func() ${resultType} { ${fn} := ${visit(node.expression)}; if ${fn} == nil { return nil }; return ${value} }()`;
|
|
5010
|
+
}
|
|
4074
5011
|
if (!isPropertyAccessExpression(node.expression)) {
|
|
4075
5012
|
return `${visit(node.expression)}(${(node.arguments ?? []).map((a) => visit(a)).join(', ')})`;
|
|
4076
5013
|
}
|
|
@@ -4189,7 +5126,7 @@ function visitArrayHigherOrderCall(node) {
|
|
|
4189
5126
|
if (!isPropertyAccessExpression(node.expression))
|
|
4190
5127
|
return undefined;
|
|
4191
5128
|
const methodName = node.expression.name.text;
|
|
4192
|
-
if (!['map', 'filter', 'some', 'find', 'findIndex', 'findLast', 'findLastIndex', 'every', 'forEach', 'reduce', 'join', 'entries', 'flatMap'].includes(methodName)) {
|
|
5129
|
+
if (!['map', 'filter', 'some', 'find', 'findIndex', 'findLast', 'findLastIndex', 'every', 'forEach', 'reduce', 'reduceRight', 'join', 'entries', 'flatMap'].includes(methodName)) {
|
|
4193
5130
|
return undefined;
|
|
4194
5131
|
}
|
|
4195
5132
|
const arrayExprNode = node.expression.expression;
|
|
@@ -4294,6 +5231,17 @@ function visitArrayHigherOrderCall(node) {
|
|
|
4294
5231
|
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
4295
5232
|
return `func() { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${callbackCall} } }()`;
|
|
4296
5233
|
}
|
|
5234
|
+
if (methodName === 'reduceRight') {
|
|
5235
|
+
importedPackages.add('slices');
|
|
5236
|
+
const reversed = { kind: 'Identifier', text: `func() []${elementType} { __s := slices.Clone(${arrayExpr}); slices.Reverse(__s); return __s }()` };
|
|
5237
|
+
variableGoTypes.set(reversed.text, ownerType ?? '[]interface{}');
|
|
5238
|
+
const access = { ...node.expression, name: { kind: 'Identifier', text: 'reduce' }, expression: reversed };
|
|
5239
|
+
const call = { ...node, expression: access };
|
|
5240
|
+
reversed.parent = access;
|
|
5241
|
+
access.parent = call;
|
|
5242
|
+
access.name.parent = access;
|
|
5243
|
+
return visitArrayHigherOrderCall(call);
|
|
5244
|
+
}
|
|
4297
5245
|
if (methodName === 'reduce') {
|
|
4298
5246
|
const reduceCallback = (node.arguments ?? [])[0];
|
|
4299
5247
|
if (!reduceCallback)
|
|
@@ -4359,16 +5307,23 @@ function getEnumMemberName(name) {
|
|
|
4359
5307
|
}
|
|
4360
5308
|
return 'Member';
|
|
4361
5309
|
}
|
|
5310
|
+
// string enums, numeric enums, or interface{} for heterogeneous ones (each member keeps its own type)
|
|
4362
5311
|
function getEnumBaseType(node) {
|
|
5312
|
+
let hasString = false;
|
|
5313
|
+
let hasNumber = false;
|
|
4363
5314
|
for (const member of (node.members ?? [])) {
|
|
4364
5315
|
const initializer = member.initializer;
|
|
4365
|
-
if (
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
5316
|
+
if (initializer && (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer))) {
|
|
5317
|
+
hasString = true;
|
|
5318
|
+
}
|
|
5319
|
+
else {
|
|
5320
|
+
hasNumber = true;
|
|
4369
5321
|
}
|
|
5322
|
+
enumMemberGoTypes.set(`${node.name.text}.${getEnumMemberName(member.name)}`, initializer && (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer)) ? 'string' : 'float64');
|
|
4370
5323
|
}
|
|
4371
|
-
|
|
5324
|
+
if (hasString && hasNumber)
|
|
5325
|
+
return 'interface{}';
|
|
5326
|
+
return hasString ? 'string' : 'float64';
|
|
4372
5327
|
}
|
|
4373
5328
|
function readNumericEnumInitializer(initializer) {
|
|
4374
5329
|
if (isNumericLiteral(initializer)) {
|
|
@@ -4381,18 +5336,28 @@ function readNumericEnumInitializer(initializer) {
|
|
|
4381
5336
|
}
|
|
4382
5337
|
return undefined;
|
|
4383
5338
|
}
|
|
4384
|
-
function
|
|
5339
|
+
function enumHasComputedMember(node) {
|
|
5340
|
+
return (node.members ?? []).some((m) => m.initializer &&
|
|
5341
|
+
!isStringLiteral(m.initializer) &&
|
|
5342
|
+
!isNoSubstitutionTemplateLiteral(m.initializer) &&
|
|
5343
|
+
readNumericEnumInitializer(m.initializer) === undefined);
|
|
5344
|
+
}
|
|
5345
|
+
function visitEnumDeclaration(node, inPlace = false) {
|
|
4385
5346
|
const enumName = getSafeName(node.name.text);
|
|
4386
5347
|
const baseType = enumBaseTypes.get(node.name.text) ?? getEnumBaseType(node);
|
|
4387
5348
|
let nextNumericValue = 0;
|
|
4388
5349
|
let canAutoIncrement = true;
|
|
5350
|
+
let hasComputedMember = false;
|
|
4389
5351
|
const members = [];
|
|
4390
5352
|
for (const member of (node.members ?? [])) {
|
|
4391
5353
|
const memberName = getEnumMemberName(member.name);
|
|
4392
5354
|
const symbolName = `${enumName}_${memberName}`;
|
|
4393
5355
|
let valueExpr;
|
|
4394
5356
|
if (member.initializer) {
|
|
4395
|
-
if (baseType === '
|
|
5357
|
+
if (baseType === 'interface{}' && (isStringLiteral(member.initializer) || isNoSubstitutionTemplateLiteral(member.initializer))) {
|
|
5358
|
+
valueExpr = visit(member.initializer);
|
|
5359
|
+
}
|
|
5360
|
+
else if (baseType !== 'string') {
|
|
4396
5361
|
const numericValue = readNumericEnumInitializer(member.initializer);
|
|
4397
5362
|
if (numericValue !== undefined) {
|
|
4398
5363
|
valueExpr = `${numericValue}`;
|
|
@@ -4402,13 +5367,14 @@ function visitEnumDeclaration(node) {
|
|
|
4402
5367
|
else {
|
|
4403
5368
|
valueExpr = `float64(${visit(member.initializer)})`;
|
|
4404
5369
|
canAutoIncrement = false;
|
|
5370
|
+
hasComputedMember = true;
|
|
4405
5371
|
}
|
|
4406
5372
|
}
|
|
4407
5373
|
else {
|
|
4408
5374
|
valueExpr = visit(member.initializer);
|
|
4409
5375
|
}
|
|
4410
5376
|
}
|
|
4411
|
-
else if (baseType
|
|
5377
|
+
else if (baseType !== 'string') {
|
|
4412
5378
|
const currentValue = canAutoIncrement ? nextNumericValue : 0;
|
|
4413
5379
|
valueExpr = `${currentValue}`;
|
|
4414
5380
|
nextNumericValue = currentValue + 1;
|
|
@@ -4418,10 +5384,37 @@ function visitEnumDeclaration(node) {
|
|
|
4418
5384
|
}
|
|
4419
5385
|
members.push(`\t${symbolName} ${enumName} = ${enumName}(${valueExpr})`);
|
|
4420
5386
|
}
|
|
4421
|
-
//
|
|
4422
|
-
|
|
5387
|
+
// Assigned in place: package variables declared, values set in source order
|
|
5388
|
+
if (inPlace) {
|
|
5389
|
+
return members
|
|
5390
|
+
.map((m) => {
|
|
5391
|
+
const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
|
|
5392
|
+
const raw = m.slice(m.indexOf(`= ${enumName}(`) + enumName.length + 3, -1);
|
|
5393
|
+
const value = /^-?[0-9.]+$/.test(raw) ? `float64(${raw})` : raw;
|
|
5394
|
+
const memberType = baseType === 'interface{}'
|
|
5395
|
+
? (enumMemberGoTypes.get(`${node.name.text}.${name.slice(enumName.length + 1)}`) ?? 'interface{}')
|
|
5396
|
+
: baseType === 'float64'
|
|
5397
|
+
? 'float64'
|
|
5398
|
+
: enumName;
|
|
5399
|
+
mainPackageVariables.push(`var ${name} ${memberType}`);
|
|
5400
|
+
return `${name} = ${memberType === enumName ? `${enumName}(${raw})` : value};\n\t`;
|
|
5401
|
+
})
|
|
5402
|
+
.join('');
|
|
5403
|
+
}
|
|
5404
|
+
// Heterogeneous enums: plain variables of each member's own type
|
|
5405
|
+
if (baseType === 'interface{}') {
|
|
5406
|
+
return `var (\n${members
|
|
5407
|
+
.map((m) => {
|
|
5408
|
+
const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
|
|
5409
|
+
const value = m.slice(m.indexOf(`= ${enumName}(`) + enumName.length + 3, -1);
|
|
5410
|
+
return `\t${name} = ${/^-?[0-9.]+$/.test(value) ? `float64(${value})` : value}`;
|
|
5411
|
+
})
|
|
5412
|
+
.join('\n')}\n)`;
|
|
5413
|
+
}
|
|
5414
|
+
// Numeric enums are plain float64 constants so they compare against numbers
|
|
5415
|
+
// (variables when a member is computed); string enums keep a named type
|
|
4423
5416
|
if (baseType === 'float64') {
|
|
4424
|
-
return
|
|
5417
|
+
return `${hasComputedMember ? 'var' : 'const'} (\n${members
|
|
4425
5418
|
.map((m) => {
|
|
4426
5419
|
const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
|
|
4427
5420
|
const value = m.slice(m.lastIndexOf('=') + 1).trim();
|
|
@@ -4444,6 +5437,35 @@ function getType(typeNode, getArrayType = false) {
|
|
|
4444
5437
|
const elementType = getType(typeNode.elementType);
|
|
4445
5438
|
return getArrayType ? elementType : `[]${elementType}`;
|
|
4446
5439
|
}
|
|
5440
|
+
if (typeNode.kind === 'MappedType')
|
|
5441
|
+
return getMappedType(typeNode);
|
|
5442
|
+
if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName) && /DecoratorContext$/.test(typeNode.typeName.text)) {
|
|
5443
|
+
useHelper('decorators');
|
|
5444
|
+
return '*TnDecoratorContext';
|
|
5445
|
+
}
|
|
5446
|
+
if (isTypeReferenceNode(typeNode) &&
|
|
5447
|
+
typeNode.typeName?.kind === 'QualifiedName' &&
|
|
5448
|
+
typeNode.typeName.left?.text === 'JSX' &&
|
|
5449
|
+
typeNode.typeName.right?.text === 'Element') {
|
|
5450
|
+
useHelper('jsx');
|
|
5451
|
+
return 'TnJsxNode';
|
|
5452
|
+
}
|
|
5453
|
+
// readonly T[] → T[]; keyof T → string
|
|
5454
|
+
if (typeNode.kind === 'TypeOperator') {
|
|
5455
|
+
return typeNode.operator === 'KeyOfKeyword' ? 'string' : getType(typeNode.type, getArrayType);
|
|
5456
|
+
}
|
|
5457
|
+
// typeof x → x's type
|
|
5458
|
+
if (typeNode.kind === 'TypeQuery' && isIdentifier(typeNode.exprName)) {
|
|
5459
|
+
return variableGoTypes.get(typeNode.exprName.text) ?? 'interface{}';
|
|
5460
|
+
}
|
|
5461
|
+
// asserts x is T → no value; x is T → bool
|
|
5462
|
+
if (typeNode.kind === 'TypePredicate')
|
|
5463
|
+
return typeNode.assertsModifier ? '' : 'bool';
|
|
5464
|
+
// A & B of object types → one struct with all their fields
|
|
5465
|
+
if (typeNode.kind === 'IntersectionType') {
|
|
5466
|
+
const fields = (typeNode.types ?? []).flatMap((t) => getObjectTypeFields(t));
|
|
5467
|
+
return fields.length > 0 ? `*struct{ ${fields.join('; ')} }` : getType(typeNode.types[0]);
|
|
5468
|
+
}
|
|
4447
5469
|
// [A, B] tuples are slices: []A when all elements share a type, else []interface{}
|
|
4448
5470
|
if (typeNode.kind === 'TupleType') {
|
|
4449
5471
|
const elementType = getTupleElementType(typeNode);
|
|
@@ -4467,8 +5489,12 @@ function getType(typeNode, getArrayType = false) {
|
|
|
4467
5489
|
return 'interface{}';
|
|
4468
5490
|
}
|
|
4469
5491
|
if (isFunctionTypeNode(typeNode)) {
|
|
5492
|
+
// (...args: T[]) => R is variadic: func(...T) R
|
|
4470
5493
|
const params = (typeNode.parameters ?? [])
|
|
4471
|
-
.map((p) =>
|
|
5494
|
+
.map((p) => {
|
|
5495
|
+
const paramType = p.type ? getType(p.type) : 'interface{}';
|
|
5496
|
+
return p.dotDotDotToken && paramType.startsWith('[]') ? `...${paramType.slice(2)}` : paramType;
|
|
5497
|
+
})
|
|
4472
5498
|
.join(', ');
|
|
4473
5499
|
const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
|
|
4474
5500
|
return `func(${params})${ret}`;
|
|
@@ -4488,6 +5514,8 @@ function getType(typeNode, getArrayType = false) {
|
|
|
4488
5514
|
// Numeric enums lowered to float64 constants have no named type
|
|
4489
5515
|
if (enumBaseTypes.get(name) === 'float64')
|
|
4490
5516
|
return 'float64';
|
|
5517
|
+
if (enumBaseTypes.get(name) === 'interface{}')
|
|
5518
|
+
return 'interface{}';
|
|
4491
5519
|
return getSafeName(name);
|
|
4492
5520
|
}
|
|
4493
5521
|
const aliasType = getAliasType(name);
|
|
@@ -4518,6 +5546,10 @@ function getType(typeNode, getArrayType = false) {
|
|
|
4518
5546
|
if (name === 'Set' && typeNode.typeArguments && typeNode.typeArguments.length === 1) {
|
|
4519
5547
|
return mapGoType(getType(typeNode.typeArguments[0]), 'struct{}');
|
|
4520
5548
|
}
|
|
5549
|
+
// Partial / Required / Readonly / Pick / Omit / NonNullable<T, ...> → T's Go type
|
|
5550
|
+
if (UTILITY_TYPES.has(name) && typeNode.typeArguments?.length) {
|
|
5551
|
+
return getType(typeNode.typeArguments[0], getArrayType);
|
|
5552
|
+
}
|
|
4521
5553
|
const typeArgs = getTypeArguments(typeNode.typeArguments);
|
|
4522
5554
|
if (classNames.has(name) || isObjectTypeName(name)) {
|
|
4523
5555
|
return `*${name}${typeArgs}`;
|
|
@@ -4550,7 +5582,10 @@ function getType(typeNode, getArrayType = false) {
|
|
|
4550
5582
|
case 'any':
|
|
4551
5583
|
return 'interface{}';
|
|
4552
5584
|
case 'void':
|
|
5585
|
+
case 'never':
|
|
4553
5586
|
return '';
|
|
5587
|
+
case 'object':
|
|
5588
|
+
return 'interface{}';
|
|
4554
5589
|
default:
|
|
4555
5590
|
return typeName;
|
|
4556
5591
|
}
|
|
@@ -4661,6 +5696,22 @@ function getAcessString(leftSide, rightSide, objectType) {
|
|
|
4661
5696
|
return 'process.env';
|
|
4662
5697
|
}
|
|
4663
5698
|
}
|
|
5699
|
+
if (leftSide === 'Math') {
|
|
5700
|
+
const mathConstants = {
|
|
5701
|
+
PI: 'math.Pi',
|
|
5702
|
+
E: 'math.E',
|
|
5703
|
+
LN2: 'math.Ln2',
|
|
5704
|
+
LN10: 'math.Ln10',
|
|
5705
|
+
LOG2E: 'math.Log2E',
|
|
5706
|
+
LOG10E: 'math.Log10E',
|
|
5707
|
+
SQRT2: 'math.Sqrt2',
|
|
5708
|
+
SQRT1_2: '(1 / math.Sqrt2)'
|
|
5709
|
+
};
|
|
5710
|
+
if (mathConstants[rightSide]) {
|
|
5711
|
+
importedPackages.add('math');
|
|
5712
|
+
return `float64(${mathConstants[rightSide]})`;
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
4664
5715
|
if (leftSide === 'Number') {
|
|
4665
5716
|
const constants = {
|
|
4666
5717
|
MAX_SAFE_INTEGER: 'float64(9007199254740991)',
|
|
@@ -4701,6 +5752,63 @@ const callHandlers = {
|
|
|
4701
5752
|
importedPackages.add('fmt');
|
|
4702
5753
|
return `fmt.Println("Elapsed time:", time.Since(${getTimerName(args[0])}))`;
|
|
4703
5754
|
},
|
|
5755
|
+
'Math.hypot': (_caller, args) => mathCall('Hypot', args),
|
|
5756
|
+
'Math.cbrt': (_caller, args) => mathCall('Cbrt', args),
|
|
5757
|
+
'Math.exp': (_caller, args) => mathCall('Exp', args),
|
|
5758
|
+
'Math.expm1': (_caller, args) => mathCall('Expm1', args),
|
|
5759
|
+
'Math.log1p': (_caller, args) => mathCall('Log1p', args),
|
|
5760
|
+
'Math.asin': (_caller, args) => mathCall('Asin', args),
|
|
5761
|
+
'Math.acos': (_caller, args) => mathCall('Acos', args),
|
|
5762
|
+
'Math.atan': (_caller, args) => mathCall('Atan', args),
|
|
5763
|
+
'Math.atan2': (_caller, args) => mathCall('Atan2', args),
|
|
5764
|
+
'Math.sinh': (_caller, args) => mathCall('Sinh', args),
|
|
5765
|
+
'Math.cosh': (_caller, args) => mathCall('Cosh', args),
|
|
5766
|
+
'Math.tanh': (_caller, args) => mathCall('Tanh', args),
|
|
5767
|
+
isFinite: (_caller, args) => {
|
|
5768
|
+
importedPackages.add('math');
|
|
5769
|
+
return `(!math.IsInf(${args[0]}, 0) && !math.IsNaN(${args[0]}))`;
|
|
5770
|
+
},
|
|
5771
|
+
encodeURIComponent: (_caller, args) => {
|
|
5772
|
+
useHelper('uri');
|
|
5773
|
+
return `TnEncodeURIComponent(${args[0]})`;
|
|
5774
|
+
},
|
|
5775
|
+
decodeURIComponent: (_caller, args) => {
|
|
5776
|
+
useHelper('uri');
|
|
5777
|
+
return `TnDecodeURIComponent(${args[0]})`;
|
|
5778
|
+
},
|
|
5779
|
+
'Object.fromEntries': (_caller, args) => {
|
|
5780
|
+
useHelper('dynamic');
|
|
5781
|
+
return `TnFromEntries(${args[0]})`;
|
|
5782
|
+
},
|
|
5783
|
+
'Date.UTC': (_caller, args) => {
|
|
5784
|
+
importedPackages.add('time');
|
|
5785
|
+
const part = (i, fallback) => (args[i] ? `int(${args[i]})` : fallback);
|
|
5786
|
+
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())`;
|
|
5787
|
+
},
|
|
5788
|
+
'Promise.resolve': (_caller, args) => {
|
|
5789
|
+
const valueType = currentCallArgTypes[0] ?? 'interface{}';
|
|
5790
|
+
return `func() chan ${valueType} { __ch := make(chan ${valueType}, 1); __ch <- ${args[0] ?? 'nil'}; return __ch }()`;
|
|
5791
|
+
},
|
|
5792
|
+
structuredClone: (_caller, args) => {
|
|
5793
|
+
useHelper('structuredClone');
|
|
5794
|
+
return `TnStructuredClone(${args[0]})`;
|
|
5795
|
+
},
|
|
5796
|
+
setInterval: (_caller, args) => {
|
|
5797
|
+
useHelper('timerWait');
|
|
5798
|
+
return `TnSetInterval(${args[1] ?? '0'}, ${args[0]})`;
|
|
5799
|
+
},
|
|
5800
|
+
clearInterval: (_caller, args) => {
|
|
5801
|
+
useHelper('timerWait');
|
|
5802
|
+
return `TnClearInterval(${args[0]})`;
|
|
5803
|
+
},
|
|
5804
|
+
queueMicrotask: (_caller, args) => {
|
|
5805
|
+
useHelper('timerWait');
|
|
5806
|
+
return `TnQueueMicrotask(${args[0]})`;
|
|
5807
|
+
},
|
|
5808
|
+
clearTimeout: (_caller, args) => {
|
|
5809
|
+
useHelper('timerWait');
|
|
5810
|
+
return `TnClearTimeout(${args[0]})`;
|
|
5811
|
+
},
|
|
4704
5812
|
'Math.random': () => {
|
|
4705
5813
|
importedPackages.add('math/rand');
|
|
4706
5814
|
return 'rand.Float64()';
|
|
@@ -4833,10 +5941,7 @@ const callHandlers = {
|
|
|
4833
5941
|
importedPackages.add('reflect');
|
|
4834
5942
|
return `(reflect.ValueOf(${args[0]}).Kind() == reflect.Slice)`;
|
|
4835
5943
|
},
|
|
4836
|
-
'String': (_caller, args) =>
|
|
4837
|
-
importedPackages.add('fmt');
|
|
4838
|
-
return `fmt.Sprintf("%v", ${args[0]})`;
|
|
4839
|
-
},
|
|
5944
|
+
'String': (_caller, args) => jsStringOf(args[0] ?? '""', currentCallArgTypes[0]),
|
|
4840
5945
|
'String.fromCharCode': (_caller, args) => `string(rune(int(${args[0] ?? '0'})))`,
|
|
4841
5946
|
// Symbol(description) — unique values for record keys
|
|
4842
5947
|
Symbol: (_caller, args) => {
|
|
@@ -4863,9 +5968,7 @@ const callHandlers = {
|
|
|
4863
5968
|
importedPackages.add('math');
|
|
4864
5969
|
return `(${args[0]} == math.Trunc(${args[0]}) && !math.IsInf(${args[0]}, 0))`;
|
|
4865
5970
|
},
|
|
4866
|
-
'Boolean': (_caller, args) => {
|
|
4867
|
-
return `(${args[0]} != nil && ${args[0]} != false && ${args[0]} != 0 && ${args[0]} != "")`;
|
|
4868
|
-
},
|
|
5971
|
+
'Boolean': (_caller, args) => `(${truthinessCheck(args[0] ?? 'false', currentCallArgTypes[0] ?? 'interface{}')})`,
|
|
4869
5972
|
};
|
|
4870
5973
|
const stringMethodHandlers = {
|
|
4871
5974
|
split: (obj, args) => {
|
|
@@ -4943,6 +6046,15 @@ const stringMethodHandlers = {
|
|
|
4943
6046
|
return `${obj}[${sliceIndex(obj, args[0])}:]`;
|
|
4944
6047
|
},
|
|
4945
6048
|
concat: (obj, args) => `${obj} + ${args.join(' + ')}`,
|
|
6049
|
+
// normalize('NFC' | 'NFD' | 'NFKC' | 'NFKD')
|
|
6050
|
+
normalize: (obj, args) => {
|
|
6051
|
+
importedPackages.add('golang.org/x/text/unicode/norm');
|
|
6052
|
+
const form = args[0] ?? '"NFC"';
|
|
6053
|
+
const literal = /^"(NFC|NFD|NFKC|NFKD)"$/.exec(form);
|
|
6054
|
+
if (literal)
|
|
6055
|
+
return `tnnorm.${literal[1]}.String(${obj})`;
|
|
6056
|
+
return `func() string { switch ${form} { case "NFD": return tnnorm.NFD.String(${obj}); case "NFKC": return tnnorm.NFKC.String(${obj}); case "NFKD": return tnnorm.NFKD.String(${obj}) }; return tnnorm.NFC.String(${obj}) }()`;
|
|
6057
|
+
},
|
|
4946
6058
|
padStart: (obj, args) => {
|
|
4947
6059
|
importedPackages.add('strings');
|
|
4948
6060
|
const pad = args[1] ?? '" "';
|
|
@@ -5011,8 +6123,16 @@ const dateMethodHandlers = {
|
|
|
5011
6123
|
getDay: (obj) => `float64(int((${obj}).Weekday()))`,
|
|
5012
6124
|
getMonth: (obj) => `float64(int((${obj}).Month()) - 1)`,
|
|
5013
6125
|
getFullYear: (obj) => `float64((${obj}).Year())`,
|
|
5014
|
-
toISOString: (obj) => `(${obj}).UTC().Format("2006-01-
|
|
5015
|
-
toJSON: (obj) => `(${obj}).UTC().Format("2006-01-
|
|
6126
|
+
toISOString: (obj) => `(${obj}).UTC().Format("2006-01-02T15:04:05.000Z07:00")`,
|
|
6127
|
+
toJSON: (obj) => `(${obj}).UTC().Format("2006-01-02T15:04:05.000Z07:00")`,
|
|
6128
|
+
getUTCMilliseconds: (obj) => `float64((${obj}).UTC().Nanosecond() / 1000000)`,
|
|
6129
|
+
getUTCSeconds: (obj) => `float64((${obj}).UTC().Second())`,
|
|
6130
|
+
getUTCMinutes: (obj) => `float64((${obj}).UTC().Minute())`,
|
|
6131
|
+
getUTCHours: (obj) => `float64((${obj}).UTC().Hour())`,
|
|
6132
|
+
getUTCDate: (obj) => `float64((${obj}).UTC().Day())`,
|
|
6133
|
+
getUTCDay: (obj) => `float64(int((${obj}).UTC().Weekday()))`,
|
|
6134
|
+
getUTCMonth: (obj) => `float64(int((${obj}).UTC().Month()) - 1)`,
|
|
6135
|
+
getUTCFullYear: (obj) => `float64((${obj}).UTC().Year())`,
|
|
5016
6136
|
toString: (obj) => `(${obj}).UTC().Format("Mon Jan 02 2006 15:04:05 GMT+0000 (UTC)")`,
|
|
5017
6137
|
toDateString: (obj) => `(${obj}).UTC().Format("Mon Jan 02 2006")`,
|
|
5018
6138
|
toTimeString: (obj) => `(${obj}).UTC().Format("15:04:05 GMT+0000 (UTC)")`,
|
|
@@ -5071,19 +6191,38 @@ const arrayMethodHandlers = {
|
|
|
5071
6191
|
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
6192
|
},
|
|
5073
6193
|
// 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
|
-
},
|
|
6194
|
+
sort: (obj, args) => sortArray(obj, args),
|
|
5084
6195
|
indexOf: (obj, args) => `func() float64 { __x := ${args[0]}; for __i, __v := range ${obj} { if ${elementEquals('__v', '__x')} { return float64(__i) } }; return float64(-1) }()`,
|
|
5085
6196
|
includes: (obj, args) => `func() bool { __x := ${args[0]}; for _, __v := range ${obj} { if ${elementEquals('__v', '__x')} { return true } }; return false }()`,
|
|
5086
|
-
concat
|
|
6197
|
+
// concat(a, [b, c], d): arrays are flattened one level, other values appended
|
|
6198
|
+
concat: (obj, args) => {
|
|
6199
|
+
const parts = args.map((arg, i) => currentCallArgTypes[i]?.startsWith('[]') ? `__r = append(__r, ${arg}...)` : `__r = append(__r, ${arg})`);
|
|
6200
|
+
return `func() []${receiverElementType()} { __r := append([]${receiverElementType()}{}, ${obj}...); ${parts.join('; ')}; return __r }()`;
|
|
6201
|
+
},
|
|
6202
|
+
toSorted: (obj, args) => {
|
|
6203
|
+
importedPackages.add('slices');
|
|
6204
|
+
return sortArray(`slices.Clone(${obj})`, args);
|
|
6205
|
+
},
|
|
6206
|
+
// copyWithin(target, start, end?): copies in place (overlap-safe) and returns the array
|
|
6207
|
+
copyWithin: (obj, args) => {
|
|
6208
|
+
const end = args[2] ? sliceIndex('__s', args[2]) : 'len(__s)';
|
|
6209
|
+
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 }()`;
|
|
6210
|
+
},
|
|
6211
|
+
// toSpliced(start, deleteCount?, ...items): a spliced copy
|
|
6212
|
+
toSpliced: (obj, args) => {
|
|
6213
|
+
importedPackages.add('slices');
|
|
6214
|
+
const elementType = receiverElementType();
|
|
6215
|
+
const end = args.length >= 2 ? `min(__start+max(int(${args[1]}), 0), len(__s))` : 'len(__s)';
|
|
6216
|
+
return `func() []${elementType} { __s := ${obj}; __start := ${sliceIndex('__s', args[0] ?? '0')}; return slices.Concat(__s[:__start:__start], []${elementType}{${args.slice(2).join(', ')}}, __s[${end}:]) }()`;
|
|
6217
|
+
},
|
|
6218
|
+
toReversed: (obj) => {
|
|
6219
|
+
importedPackages.add('slices');
|
|
6220
|
+
return `func() []${receiverElementType()} { __s := slices.Clone(${obj}); slices.Reverse(__s); return __s }()`;
|
|
6221
|
+
},
|
|
6222
|
+
with: (obj, args) => {
|
|
6223
|
+
importedPackages.add('slices');
|
|
6224
|
+
return `func() []${receiverElementType()} { __s := slices.Clone(${obj}); __s[${sliceIndex('__s', args[0])}] = ${args[1]}; return __s }()`;
|
|
6225
|
+
},
|
|
5087
6226
|
// [][]T → []T (one level, like JS's default depth)
|
|
5088
6227
|
flat: (obj) => {
|
|
5089
6228
|
const elementType = receiverElementType();
|
|
@@ -5148,12 +6287,25 @@ const setMethodHandlers = {
|
|
|
5148
6287
|
has: (obj, args) => `${obj}.Has(${args[0]})`,
|
|
5149
6288
|
delete: (obj, args) => `${obj}.Delete(${args[0]})`,
|
|
5150
6289
|
clear: (obj) => `${obj}.Clear()`,
|
|
5151
|
-
values: (obj) => `${obj}.Keys()
|
|
6290
|
+
values: (obj) => `${obj}.Keys()`,
|
|
6291
|
+
union: (obj, args) => `${obj}.Union(${args[0]})`,
|
|
6292
|
+
intersection: (obj, args) => `${obj}.Intersection(${args[0]})`,
|
|
6293
|
+
difference: (obj, args) => `${obj}.Difference(${args[0]})`,
|
|
6294
|
+
symmetricDifference: (obj, args) => `${obj}.SymmetricDifference(${args[0]})`,
|
|
6295
|
+
isSubsetOf: (obj, args) => `${obj}.IsSubsetOf(${args[0]})`,
|
|
6296
|
+
isSupersetOf: (obj, args) => `${args[0]}.IsSubsetOf(${obj})`,
|
|
6297
|
+
isDisjointFrom: (obj, args) => `(${obj}.Intersection(${args[0]}).Len() == 0)`
|
|
5152
6298
|
};
|
|
5153
6299
|
function getDynamicCallHandler(caller, objectType) {
|
|
5154
6300
|
if (promiseResolveName && caller === promiseResolveName) {
|
|
5155
6301
|
return (_caller, args) => `ch <- ${args[0]}`;
|
|
5156
6302
|
}
|
|
6303
|
+
if (promiseRejectName && caller === promiseRejectName) {
|
|
6304
|
+
return (_caller, args) => {
|
|
6305
|
+
useHelper('promise');
|
|
6306
|
+
return `TnReject(ch, ${args[0] ?? 'nil'})`;
|
|
6307
|
+
};
|
|
6308
|
+
}
|
|
5157
6309
|
const dotIndex = caller.lastIndexOf('.');
|
|
5158
6310
|
if (dotIndex !== -1) {
|
|
5159
6311
|
const methodName = caller.substring(dotIndex + 1);
|
|
@@ -5168,6 +6320,12 @@ function getDynamicCallHandler(caller, objectType) {
|
|
|
5168
6320
|
return jsStringOf(obj, objectType === 'number' || objectType === 'float64' ? 'float64' : undefined);
|
|
5169
6321
|
};
|
|
5170
6322
|
}
|
|
6323
|
+
if (methodName === 'toPrecision') {
|
|
6324
|
+
return (c, args) => {
|
|
6325
|
+
importedPackages.add('strconv');
|
|
6326
|
+
return `strconv.FormatFloat(${c.substring(0, dotIndex)}, 'g', int(${args[0] ?? '-1'}), 64)`;
|
|
6327
|
+
};
|
|
6328
|
+
}
|
|
5171
6329
|
// number.toFixed(digits)
|
|
5172
6330
|
if (methodName === 'toFixed') {
|
|
5173
6331
|
return (c, args) => {
|
|
@@ -5289,7 +6447,9 @@ function isObjectKeysOfDynamic(node) {
|
|
|
5289
6447
|
isDynamicValue(node.arguments[0]));
|
|
5290
6448
|
}
|
|
5291
6449
|
const ARRAY_ONLY_METHODS = new Set([
|
|
5292
|
-
'map', 'filter', 'some', 'every', 'find', 'findIndex', '
|
|
6450
|
+
'map', 'filter', 'some', 'every', 'find', 'findIndex', 'findLast', 'findLastIndex', 'forEach', 'reduce',
|
|
6451
|
+
'reduceRight', 'join', 'flat', 'flatMap', 'sort', 'reverse', 'toSorted', 'toReversed', 'fill', 'splice',
|
|
6452
|
+
'copyWithin', 'toSpliced'
|
|
5293
6453
|
]);
|
|
5294
6454
|
const STRING_ONLY_METHODS = new Set([
|
|
5295
6455
|
'startsWith', 'endsWith', 'trim', 'trimStart', 'trimEnd', 'toUpperCase', 'toLowerCase', 'split',
|
|
@@ -5341,7 +6501,7 @@ function getSortComparatorElementType(fn) {
|
|
|
5341
6501
|
const call = fn.parent;
|
|
5342
6502
|
if (!isCallExpression(call) || call.arguments?.[0] !== fn)
|
|
5343
6503
|
return undefined;
|
|
5344
|
-
if (!isPropertyAccessExpression(call.expression) || call.expression.name.text
|
|
6504
|
+
if (!isPropertyAccessExpression(call.expression) || !['sort', 'toSorted'].includes(call.expression.name.text)) {
|
|
5345
6505
|
return undefined;
|
|
5346
6506
|
}
|
|
5347
6507
|
const arrayType = inferExpressionType(call.expression.expression);
|
|
@@ -5441,6 +6601,9 @@ function jsRegexFlagsToGo(flags) {
|
|
|
5441
6601
|
// groups to RE2 syntax: (?<name> → (?P<name>
|
|
5442
6602
|
function translateJsRegexPattern(pattern) {
|
|
5443
6603
|
return pattern
|
|
6604
|
+
// \u{1F600} / \u00e9 → RE2's \x{…}
|
|
6605
|
+
.replace(/\\u\{([0-9A-Fa-f]+)\}/g, '\\x{$1}')
|
|
6606
|
+
.replace(/\\u([0-9A-Fa-f]{4})/g, '\\x{$1}')
|
|
5444
6607
|
.replace(/\\/g, '\\\\')
|
|
5445
6608
|
.replace(/"/g, '\\"')
|
|
5446
6609
|
.replace(/\(\?<([A-Za-z_][A-Za-z0-9_]*)>/g, '(?P<$1>');
|
|
@@ -5449,12 +6612,267 @@ function translateJsRegexPattern(pattern) {
|
|
|
5449
6612
|
// Named groups (?<name> are fine (translated to (?P<name>), so strip them
|
|
5450
6613
|
// before testing for lookarounds (?= (?! (?<= (?<!
|
|
5451
6614
|
function needsStatefulRegex(pattern, flags) {
|
|
5452
|
-
|
|
6615
|
+
// /g and /y keep lastIndex state; /u keeps its flag for .flags
|
|
6616
|
+
if (flags.includes('g') || flags.includes('y') || flags.includes('u'))
|
|
5453
6617
|
return true;
|
|
5454
6618
|
const stripped = pattern.replace(/\(\?<[A-Za-z_][A-Za-z0-9_]*>/g, '');
|
|
5455
6619
|
// (?= (?! and any remaining (?< (lookbehind) need the backtracker
|
|
5456
6620
|
return /\\[1-9]|\(\?[=!<]/.test(stripped);
|
|
5457
6621
|
}
|
|
6622
|
+
const REGEX_FLAG_PROPERTIES = {
|
|
6623
|
+
global: 'g', ignoreCase: 'i', multiline: 'm', dotAll: 's', unicode: 'u', sticky: 'y'
|
|
6624
|
+
};
|
|
6625
|
+
function inferRegexPropertyType(expr) {
|
|
6626
|
+
if (!isPropertyAccessExpression(expr))
|
|
6627
|
+
return undefined;
|
|
6628
|
+
const receiverType = inferExpressionType(expr.expression);
|
|
6629
|
+
if (receiverType !== '*TnRegex' && receiverType !== '*regexp.Regexp')
|
|
6630
|
+
return undefined;
|
|
6631
|
+
const name = expr.name.text;
|
|
6632
|
+
if (name === 'lastIndex')
|
|
6633
|
+
return 'float64';
|
|
6634
|
+
if (name === 'source' || name === 'flags')
|
|
6635
|
+
return 'string';
|
|
6636
|
+
if (REGEX_FLAG_PROPERTIES[name])
|
|
6637
|
+
return 'bool';
|
|
6638
|
+
return undefined;
|
|
6639
|
+
}
|
|
6640
|
+
function visitRegexProperty(node) {
|
|
6641
|
+
const type = inferRegexPropertyType(node);
|
|
6642
|
+
if (!type || isCallee(node))
|
|
6643
|
+
return undefined;
|
|
6644
|
+
const name = node.name.text;
|
|
6645
|
+
const receiver = visit(node.expression);
|
|
6646
|
+
useHelper('fancyRegex');
|
|
6647
|
+
if (inferExpressionType(node.expression) === '*TnRegex') {
|
|
6648
|
+
if (name === 'lastIndex' || name === 'source' || name === 'flags')
|
|
6649
|
+
return `${receiver}.${name}`;
|
|
6650
|
+
importedPackages.add('strings');
|
|
6651
|
+
return `strings.Contains(${receiver}.flags, "${REGEX_FLAG_PROPERTIES[name]}")`;
|
|
6652
|
+
}
|
|
6653
|
+
// plain RE2 regexes have no state; their flags (i/m/s) lead the pattern
|
|
6654
|
+
if (name === 'lastIndex')
|
|
6655
|
+
return 'float64(0)';
|
|
6656
|
+
if (name === 'source')
|
|
6657
|
+
return `TnRegexpSource(${receiver})`;
|
|
6658
|
+
if (name === 'flags')
|
|
6659
|
+
return `TnRegexpFlags(${receiver})`;
|
|
6660
|
+
importedPackages.add('strings');
|
|
6661
|
+
return `strings.Contains(TnRegexpFlags(${receiver}), "${REGEX_FLAG_PROPERTIES[name]}")`;
|
|
6662
|
+
}
|
|
6663
|
+
// Whether an expression is used as a value (argument, initializer, element, assigned or returned)
|
|
6664
|
+
function isValuePosition(node) {
|
|
6665
|
+
const parent = node.parent;
|
|
6666
|
+
if (!parent)
|
|
6667
|
+
return false;
|
|
6668
|
+
if (isCallExpression(parent))
|
|
6669
|
+
return (parent.arguments ?? []).includes(node);
|
|
6670
|
+
if (isVariableDeclaration(parent) || isPropertyAssignment(parent))
|
|
6671
|
+
return parent.initializer === node;
|
|
6672
|
+
if (isArrayLiteralExpression(parent))
|
|
6673
|
+
return true;
|
|
6674
|
+
if (isReturnStatement(parent))
|
|
6675
|
+
return true;
|
|
6676
|
+
return isBinaryExpression(parent) && parent.operatorToken.kind === 'EqualsToken' && parent.right === node;
|
|
6677
|
+
}
|
|
6678
|
+
function isJsxNode(node) {
|
|
6679
|
+
return node?.kind === 'JsxElement' || node?.kind === 'JsxSelfClosingElement' || node?.kind === 'JsxFragment';
|
|
6680
|
+
}
|
|
6681
|
+
// JSX text: lines trimmed at their inner edges, blank lines dropped, joined by a space
|
|
6682
|
+
function cleanJsxText(text) {
|
|
6683
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
6684
|
+
const kept = [];
|
|
6685
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6686
|
+
let line = lines[i].replace(/\t/g, ' ');
|
|
6687
|
+
if (i > 0)
|
|
6688
|
+
line = line.replace(/^ +/, '');
|
|
6689
|
+
if (i < lines.length - 1)
|
|
6690
|
+
line = line.replace(/ +$/, '');
|
|
6691
|
+
if (line)
|
|
6692
|
+
kept.push(line);
|
|
6693
|
+
}
|
|
6694
|
+
return kept.join(' ');
|
|
6695
|
+
}
|
|
6696
|
+
// The children of an element as []interface{} (text, {expressions}, nested elements)
|
|
6697
|
+
function visitJsxChildren(children) {
|
|
6698
|
+
const parts = [];
|
|
6699
|
+
for (const child of children) {
|
|
6700
|
+
if (child.kind === 'JsxText') {
|
|
6701
|
+
const text = cleanJsxText(child.text ?? '');
|
|
6702
|
+
if (text)
|
|
6703
|
+
parts.push(toGoStringLiteral(text));
|
|
6704
|
+
}
|
|
6705
|
+
else if (child.kind === 'JsxExpression') {
|
|
6706
|
+
if (child.expression)
|
|
6707
|
+
parts.push(visit(child.expression));
|
|
6708
|
+
}
|
|
6709
|
+
else {
|
|
6710
|
+
parts.push(visit(child));
|
|
6711
|
+
}
|
|
6712
|
+
}
|
|
6713
|
+
return `[]interface{}{${parts.join(', ')}}`;
|
|
6714
|
+
}
|
|
6715
|
+
// An attribute's value: name="x", name={expr}, or a bare name (true)
|
|
6716
|
+
function getJsxAttributeValue(attribute) {
|
|
6717
|
+
if (!attribute.initializer) {
|
|
6718
|
+
const trueNode = { kind: 'TrueKeyword', parent: attribute };
|
|
6719
|
+
return trueNode;
|
|
6720
|
+
}
|
|
6721
|
+
return attribute.initializer.kind === 'JsxExpression' ? attribute.initializer.expression : attribute.initializer;
|
|
6722
|
+
}
|
|
6723
|
+
function visitJsx(node) {
|
|
6724
|
+
useHelper('jsx');
|
|
6725
|
+
if (node.kind === 'JsxFragment')
|
|
6726
|
+
return `TnJsxChildren(${visitJsxChildren(node.children ?? [])})`;
|
|
6727
|
+
const opening = node.kind === 'JsxElement' ? node.openingElement : node;
|
|
6728
|
+
const children = node.kind === 'JsxElement' ? (node.children ?? []) : [];
|
|
6729
|
+
const tag = opening.tagName;
|
|
6730
|
+
const attributes = (opening.attributes?.properties ?? []).filter((a) => a.kind === 'JsxAttribute');
|
|
6731
|
+
// <div …> / <my-element …>: an HTML element
|
|
6732
|
+
if (isIdentifier(tag) && (/^[a-z]/.test(tag.text) || tag.text.includes('-'))) {
|
|
6733
|
+
const attrs = attributes.map((a) => `{${toGoStringLiteral(a.name.text)}, ${visit(getJsxAttributeValue(a))}}`);
|
|
6734
|
+
return `TnJsxElement(${toGoStringLiteral(tag.text)}, []TnJsxAttr{${attrs.join(', ')}}, ${visitJsxChildren(children)})`;
|
|
6735
|
+
}
|
|
6736
|
+
// <Card title="x">…</Card> → Card({ title: "x", children: … })
|
|
6737
|
+
const call = { kind: 'CallExpression', expression: tag, arguments: [], parent: node.parent };
|
|
6738
|
+
tag.parent = call;
|
|
6739
|
+
const properties = [];
|
|
6740
|
+
for (const a of attributes) {
|
|
6741
|
+
const name = { kind: 'Identifier', text: a.name.text };
|
|
6742
|
+
const property = { kind: 'PropertyAssignment', name, initializer: getJsxAttributeValue(a) };
|
|
6743
|
+
property.initializer.parent = property;
|
|
6744
|
+
name.parent = property;
|
|
6745
|
+
properties.push(property);
|
|
6746
|
+
}
|
|
6747
|
+
const meaningful = children.filter((c) => c.kind !== 'JsxText' || cleanJsxText(c.text ?? ''));
|
|
6748
|
+
if (meaningful.length > 0) {
|
|
6749
|
+
const name = { kind: 'Identifier', text: 'children' };
|
|
6750
|
+
const fragment = { kind: 'JsxFragment', children };
|
|
6751
|
+
const property = { kind: 'PropertyAssignment', name, initializer: fragment };
|
|
6752
|
+
fragment.parent = property;
|
|
6753
|
+
name.parent = property;
|
|
6754
|
+
properties.push(property);
|
|
6755
|
+
}
|
|
6756
|
+
const props = { kind: 'ObjectLiteralExpression', properties, parent: call };
|
|
6757
|
+
for (const property of properties)
|
|
6758
|
+
property.parent = props;
|
|
6759
|
+
const declared = isIdentifier(tag) ? declaredFunctions.get(tag.text) : undefined;
|
|
6760
|
+
if (properties.length > 0 || (declared?.parameters ?? []).length > 0) {
|
|
6761
|
+
const args = [props];
|
|
6762
|
+
call.arguments = args;
|
|
6763
|
+
}
|
|
6764
|
+
return visit(call);
|
|
6765
|
+
}
|
|
6766
|
+
function getDecorators(node) {
|
|
6767
|
+
return (node.modifiers ?? []).filter((m) => m.kind === 'Decorator');
|
|
6768
|
+
}
|
|
6769
|
+
// `this` inside a function / function expression (not a class member, not an arrow's)
|
|
6770
|
+
function isThisOfPlainFunction(node) {
|
|
6771
|
+
for (let n = node.parent; n; n = n.parent) {
|
|
6772
|
+
if (isArrowFunction(n))
|
|
6773
|
+
continue;
|
|
6774
|
+
if (isFunctionExpression(n) || isFunctionDeclaration(n))
|
|
6775
|
+
return !isPropertyAssignment(n.parent);
|
|
6776
|
+
if (isMethodDeclaration(n) || isConstructorDeclaration(n) || isPropertyDeclaration(n) || isGetAccessor(n) || isSetAccessor(n) || isClassDeclaration(n))
|
|
6777
|
+
return false;
|
|
6778
|
+
}
|
|
6779
|
+
return false;
|
|
6780
|
+
}
|
|
6781
|
+
// f.call(thisArg, a, b) → f(a, b); f.apply(thisArg, args) → f(args...) (this is TnThis)
|
|
6782
|
+
function visitFunctionCallApply(node) {
|
|
6783
|
+
if (!isPropertyAccessExpression(node.expression))
|
|
6784
|
+
return undefined;
|
|
6785
|
+
const method = node.expression.name.text;
|
|
6786
|
+
if (method !== 'call' && method !== 'apply')
|
|
6787
|
+
return undefined;
|
|
6788
|
+
if (!inferExpressionType(node.expression.expression)?.startsWith('func('))
|
|
6789
|
+
return undefined;
|
|
6790
|
+
const fn = visit(node.expression.expression);
|
|
6791
|
+
const args = (node.arguments ?? []).slice(1);
|
|
6792
|
+
if (method === 'apply')
|
|
6793
|
+
return `${fn}(${args[0] ? `${visit(args[0])}...` : ''})`;
|
|
6794
|
+
return `${fn}(${args.map((a) => (isSpreadElement(a) ? `${visit(a.expression)}...` : visit(a))).join(', ')})`;
|
|
6795
|
+
}
|
|
6796
|
+
// Whether a decorator call returns a value (a replacement), or only has effects
|
|
6797
|
+
function decoratorReturnsValue(decorator) {
|
|
6798
|
+
let fnType = inferExpressionType(decorator.expression);
|
|
6799
|
+
// @factory(args): the decorator is what the factory returns
|
|
6800
|
+
const factoryCall = decorator.expression;
|
|
6801
|
+
if (isCallExpression(factoryCall) && isIdentifier(factoryCall.expression)) {
|
|
6802
|
+
const factory = declaredFunctions.get(factoryCall.expression.text);
|
|
6803
|
+
if (factory)
|
|
6804
|
+
fnType = factory.type ? getType(factory.type) : inferFunctionBodyReturnType(factory);
|
|
6805
|
+
}
|
|
6806
|
+
if (fnType?.startsWith('func('))
|
|
6807
|
+
return !!goFuncReturnType(fnType);
|
|
6808
|
+
const call = { kind: 'CallExpression', expression: decorator.expression, arguments: [], parent: decorator };
|
|
6809
|
+
const returnType = inferExpressionType(call);
|
|
6810
|
+
return !!returnType && returnType !== 'struct{}';
|
|
6811
|
+
}
|
|
6812
|
+
// The decorated method m: m__undecorated holds the body; m calls the decorated function
|
|
6813
|
+
// (Owner_m__decorated) with TnThis set to the receiver
|
|
6814
|
+
function emitDecoratedMethod(className, typeParamNames, member, ownerClass) {
|
|
6815
|
+
useHelper('decorators');
|
|
6816
|
+
useHelper('dynamic');
|
|
6817
|
+
const methodName = visit(member.name);
|
|
6818
|
+
const info = getFunctionParametersInfo(member.parameters ?? []);
|
|
6819
|
+
if (info.signature.includes('...'))
|
|
6820
|
+
throw new Error(`decorated method ${methodName}: rest/default parameters are not supported`);
|
|
6821
|
+
const names = splitGoParameters(info.signature).map((p) => p.slice(0, p.indexOf(' ')));
|
|
6822
|
+
const returnType = member.type ? getType(member.type) : '';
|
|
6823
|
+
const result = returnType ? ` ${returnType}` : '';
|
|
6824
|
+
const body = visit(member.body, { prefixBlockContent: info.prefixBlockContent });
|
|
6825
|
+
const call = `${ownerClass}_${methodName}__decorated(${names.join(', ')})`;
|
|
6826
|
+
const invoke = returnType ? `return TnAs[${returnType}](${call})` : call;
|
|
6827
|
+
if ((member.modifiers ?? []).some((m) => m.kind === 'StaticKeyword')) {
|
|
6828
|
+
return `func ${className}_${methodName}__undecorated(${info.signature})${result} ${body}\n\nfunc ${className}_${methodName}(${info.signature})${result} {\n\t\t${invoke}\n\t}\n\n`;
|
|
6829
|
+
}
|
|
6830
|
+
return (`func (self *${className}${typeParamNames}) ${methodName}__undecorated(${info.signature})${result} ${body}\n\n` +
|
|
6831
|
+
`func (self *${className}${typeParamNames}) ${methodName}(${info.signature})${result} {\n\t\t__prevThis := TnThis\n\t\tTnThis = self\n\t\tdefer func() { TnThis = __prevThis }()\n\t\t${invoke}\n\t}\n\n`);
|
|
6832
|
+
}
|
|
6833
|
+
// Applies a class's decorators where it is defined (in source order): each decorated
|
|
6834
|
+
// method's function is wrapped bottom-up, then class decorators run
|
|
6835
|
+
function visitClassDecorators(node) {
|
|
6836
|
+
const className = visit(node.name);
|
|
6837
|
+
const statements = [];
|
|
6838
|
+
for (const member of node.members ?? []) {
|
|
6839
|
+
const decorators = getDecorators(member);
|
|
6840
|
+
if (decorators.length === 0)
|
|
6841
|
+
continue;
|
|
6842
|
+
if (!isMethodDeclaration(member) || !member.body)
|
|
6843
|
+
throw new Error(`${className}: only method and class decorators are supported`);
|
|
6844
|
+
useHelper('decorators');
|
|
6845
|
+
useHelper('dynamic');
|
|
6846
|
+
const methodName = visit(member.name);
|
|
6847
|
+
const isStatic = (member.modifiers ?? []).some((m) => m.kind === 'StaticKeyword');
|
|
6848
|
+
const isPrivate = member.name?.kind === 'PrivateIdentifier';
|
|
6849
|
+
const params = splitGoParameters(getFunctionParametersInfo(member.parameters ?? []).signature);
|
|
6850
|
+
const paramTypes = params.map((p) => p.slice(p.indexOf(' ') + 1));
|
|
6851
|
+
const returnType = member.type ? getType(member.type) : '';
|
|
6852
|
+
const varName = `${className}_${methodName}__decorated`;
|
|
6853
|
+
mainPackageVariables.push(`var ${varName} func(...interface{}) interface{}`);
|
|
6854
|
+
const args = paramTypes.map((t, i) => `TnAs[${t}](__args[${i}])`).join(', ');
|
|
6855
|
+
const target = isStatic
|
|
6856
|
+
? `${className}_${methodName}__undecorated(${args})`
|
|
6857
|
+
: `TnThis.(interface{ ${methodName}__undecorated(${paramTypes.join(', ')})${returnType ? ` ${returnType}` : ''} }).${methodName}__undecorated(${args})`;
|
|
6858
|
+
statements.push(`${varName} = func(__args ...interface{}) interface{} { ${returnType ? `return ${target}` : `${target}; return nil`} }`);
|
|
6859
|
+
for (const decorator of [...decorators].reverse()) {
|
|
6860
|
+
const context = `&TnDecoratorContext{kind: "method", name: ${toGoStringLiteral(isPrivate ? member.name.text : methodName)}, static: ${isStatic}, private: ${isPrivate}}`;
|
|
6861
|
+
const call = `(${visit(decorator.expression)})(${varName}, ${context})`;
|
|
6862
|
+
statements.push(decoratorReturnsValue(decorator) ? `${varName} = TnDecorate(${varName}, ${call})` : call);
|
|
6863
|
+
}
|
|
6864
|
+
}
|
|
6865
|
+
for (const decorator of [...getDecorators(node)].reverse()) {
|
|
6866
|
+
useHelper('decorators');
|
|
6867
|
+
const context = `&TnDecoratorContext{kind: "class", name: ${toGoStringLiteral(className)}}`;
|
|
6868
|
+
const call = `(${visit(decorator.expression)})(New${className}, ${context})`;
|
|
6869
|
+
statements.push(decoratorReturnsValue(decorator) ? `TnDecorateClass(${call})` : call);
|
|
6870
|
+
}
|
|
6871
|
+
return statements.map((statement) => `${statement};\n\t`).join('');
|
|
6872
|
+
}
|
|
6873
|
+
function isStringRawTag(tag) {
|
|
6874
|
+
return isPropertyAccessExpression(tag) && isIdentifier(tag.expression) && tag.expression.text === 'String' && tag.name.text === 'raw';
|
|
6875
|
+
}
|
|
5458
6876
|
// Whether a visited regex argument is the stateful wrapper type
|
|
5459
6877
|
function isTnRegexArg(arg) {
|
|
5460
6878
|
return arg.startsWith('TnRegexCompile(') || variableGoTypes.get(arg) === '*TnRegex';
|
|
@@ -5465,7 +6883,8 @@ function getTypeParameters(typeParameters) {
|
|
|
5465
6883
|
const params = typeParameters.map((tp) => {
|
|
5466
6884
|
const name = visit(tp.name);
|
|
5467
6885
|
const constraint = tp.constraint ? getType(tp.constraint) : 'any';
|
|
5468
|
-
|
|
6886
|
+
// Go constraints cannot require fields: object-type bounds become any
|
|
6887
|
+
return `${name} ${constraint.startsWith('*') ? 'any' : constraint}`;
|
|
5469
6888
|
});
|
|
5470
6889
|
return `[${params.join(', ')}]`;
|
|
5471
6890
|
}
|
|
@@ -5626,6 +7045,14 @@ function visitCallArguments(node) {
|
|
|
5626
7045
|
return result;
|
|
5627
7046
|
}
|
|
5628
7047
|
function withContextualParameters(fn, info) {
|
|
7048
|
+
if (usesArgumentsObject(fn) && !info.signature.includes('...')) {
|
|
7049
|
+
const names = (fn.parameters ?? []).map((p) => getParameterName(p, fn.parameters));
|
|
7050
|
+
registerLocalVariable('arguments', '[]interface{}');
|
|
7051
|
+
return {
|
|
7052
|
+
signature: [info.signature, '__arguments ...interface{}'].filter((part) => part).join(', '),
|
|
7053
|
+
prefixBlockContent: `${info.prefixBlockContent}arguments := append([]interface{}{${names.join(', ')}}, __arguments...)\n\t\t_ = arguments\n\t\t`
|
|
7054
|
+
};
|
|
7055
|
+
}
|
|
5629
7056
|
const contextualFn = getContextualFunctionType(fn);
|
|
5630
7057
|
const declaredCount = (fn.parameters ?? []).length;
|
|
5631
7058
|
const contextualParams = contextualFn?.parameters ?? [];
|
|
@@ -5637,6 +7064,16 @@ function withContextualParameters(fn, info) {
|
|
|
5637
7064
|
const signature = [info.signature, ...extra].filter((part) => part).join(', ');
|
|
5638
7065
|
return { ...info, signature };
|
|
5639
7066
|
}
|
|
7067
|
+
// A non-arrow function whose own body reads `arguments` (arrows see their parent's)
|
|
7068
|
+
function usesArgumentsObject(fn) {
|
|
7069
|
+
if (isArrowFunction(fn) || !fn.body)
|
|
7070
|
+
return false;
|
|
7071
|
+
return containsNode(fn.body, (n) => {
|
|
7072
|
+
if (n !== fn.body && (isFunctionDeclaration(n) || isFunctionExpression(n) || isMethodDeclaration(n)))
|
|
7073
|
+
return false;
|
|
7074
|
+
return isIdentifier(n) && n.text === 'arguments' && !isFieldNameIdentifier(n);
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
5640
7077
|
// Destructured parameters ({ a, b }: T / [x, y]: T[]) get a temporary name
|
|
5641
7078
|
function getParameterName(param, parameters) {
|
|
5642
7079
|
if (isIdentifier(param.name))
|
|
@@ -5737,7 +7174,8 @@ function getPromiseChannelType(node) {
|
|
|
5737
7174
|
while (parent) {
|
|
5738
7175
|
if (isFunctionDeclaration(parent) ||
|
|
5739
7176
|
isMethodDeclaration(parent) ||
|
|
5740
|
-
isFunctionExpression(parent)
|
|
7177
|
+
isFunctionExpression(parent) ||
|
|
7178
|
+
isArrowFunction(parent)) {
|
|
5741
7179
|
if (parent.type &&
|
|
5742
7180
|
isTypeReferenceNode(parent.type) &&
|
|
5743
7181
|
isIdentifier(parent.type.typeName)) {
|
|
@@ -5764,6 +7202,9 @@ function emitChannelFunction(node, isAsync) {
|
|
|
5764
7202
|
}
|
|
5765
7203
|
const chanType = isAsync ? getPromiseChannelType(node) : getGeneratorYieldType(node);
|
|
5766
7204
|
const channelVar = '__ch';
|
|
7205
|
+
if (isAsync)
|
|
7206
|
+
useHelper('promise');
|
|
7207
|
+
const rejectOnPanic = isAsync ? `\n\t\tdefer TnRejectOnPanic(${channelVar})` : '';
|
|
5767
7208
|
const prevChannel = asyncChannelVar;
|
|
5768
7209
|
const prevFn = asyncFunctionNode;
|
|
5769
7210
|
asyncChannelVar = channelVar;
|
|
@@ -5771,7 +7212,7 @@ function emitChannelFunction(node, isAsync) {
|
|
|
5771
7212
|
const body = visit(node.body, { prefixBlockContent: parameterInfo.prefixBlockContent });
|
|
5772
7213
|
asyncChannelVar = prevChannel;
|
|
5773
7214
|
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}`);
|
|
7215
|
+
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
7216
|
}
|
|
5776
7217
|
// The type a generator yields: from its first yield expression's operand
|
|
5777
7218
|
function getGeneratorYieldType(fn) {
|
|
@@ -5793,10 +7234,14 @@ function visitPromiseReturn(node, options) {
|
|
|
5793
7234
|
const channelType = getPromiseChannelType(node);
|
|
5794
7235
|
const resolveParam = callback.parameters[0];
|
|
5795
7236
|
const resolveName = resolveParam ? visit(resolveParam.name) : '';
|
|
7237
|
+
const rejectParam = callback.parameters[1];
|
|
5796
7238
|
const prevResolveName = promiseResolveName;
|
|
7239
|
+
const prevRejectName = promiseRejectName;
|
|
5797
7240
|
promiseResolveName = resolveName;
|
|
7241
|
+
promiseRejectName = rejectParam ? visit(rejectParam.name) : '';
|
|
5798
7242
|
const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
|
|
5799
7243
|
promiseResolveName = prevResolveName;
|
|
7244
|
+
promiseRejectName = prevRejectName;
|
|
5800
7245
|
return (`ch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch` +
|
|
5801
7246
|
(options.inline ? '' : ';\n\t'));
|
|
5802
7247
|
}
|
|
@@ -5808,10 +7253,14 @@ function visitNewPromise(node) {
|
|
|
5808
7253
|
const channelType = getPromiseChannelType(node);
|
|
5809
7254
|
const resolveParam = callback.parameters[0];
|
|
5810
7255
|
const resolveName = resolveParam ? visit(resolveParam.name) : '';
|
|
7256
|
+
const rejectParam = callback.parameters[1];
|
|
5811
7257
|
const prevResolveName = promiseResolveName;
|
|
7258
|
+
const prevRejectName = promiseRejectName;
|
|
5812
7259
|
promiseResolveName = resolveName;
|
|
7260
|
+
promiseRejectName = rejectParam ? visit(rejectParam.name) : '';
|
|
5813
7261
|
const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
|
|
5814
7262
|
promiseResolveName = prevResolveName;
|
|
7263
|
+
promiseRejectName = prevRejectName;
|
|
5815
7264
|
return `func() chan ${channelType} {\n\t\tch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch;\n\t}()`;
|
|
5816
7265
|
}
|
|
5817
7266
|
function extractMapValueType(mapType) {
|
|
@@ -5865,8 +7314,8 @@ function visitNewSet(node) {
|
|
|
5865
7314
|
elementType = getArrayLiteralElementType(node.arguments[0]);
|
|
5866
7315
|
}
|
|
5867
7316
|
const args = node.arguments ?? [];
|
|
5868
|
-
// new Set(otherSet/values()) — copy at runtime
|
|
5869
|
-
if (args.length > 0 && !isArrayLiteralExpression(args[0])) {
|
|
7317
|
+
// new Set(otherSet/values()) / new Set([...a, ...b]) — copy at runtime
|
|
7318
|
+
if (args.length > 0 && (!isArrayLiteralExpression(args[0]) || (args[0].elements ?? []).some((e) => isSpreadElement(e)))) {
|
|
5870
7319
|
useHelper('orderedMap');
|
|
5871
7320
|
const src = getTempName('setsrc');
|
|
5872
7321
|
const item = getTempName('item');
|
|
@@ -5901,9 +7350,11 @@ function normalizeCjsToEsm(code) {
|
|
|
5901
7350
|
code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
|
|
5902
7351
|
return code;
|
|
5903
7352
|
}
|
|
5904
|
-
function includeLocalImport(code, dir, goFileName) {
|
|
7353
|
+
function includeLocalImport(code, dir, goFileName, tsx = false) {
|
|
5905
7354
|
const prevDir = currentFileDir;
|
|
7355
|
+
const prevTsx = currentFileIsTsx;
|
|
5906
7356
|
currentFileDir = dir;
|
|
7357
|
+
currentFileIsTsx = tsx;
|
|
5907
7358
|
// Normalize CommonJS modules to ES module syntax before parsing
|
|
5908
7359
|
if (!code.includes('export ') && (code.includes('module.exports') || code.includes('exports.'))) {
|
|
5909
7360
|
code = normalizeCjsToEsm(code);
|
|
@@ -5915,6 +7366,7 @@ function includeLocalImport(code, dir, goFileName) {
|
|
|
5915
7366
|
visit(stmt, { addFunctionOutside: true });
|
|
5916
7367
|
}
|
|
5917
7368
|
currentFileDir = prevDir;
|
|
7369
|
+
currentFileIsTsx = prevTsx;
|
|
5918
7370
|
return;
|
|
5919
7371
|
}
|
|
5920
7372
|
// Separate-file mode (local TS imports): transpile to its own Go file.
|
|
@@ -5936,7 +7388,13 @@ function includeLocalImport(code, dir, goFileName) {
|
|
|
5936
7388
|
}
|
|
5937
7389
|
emittingModuleFile = prevEmittingModuleFile;
|
|
5938
7390
|
const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
5939
|
-
const
|
|
7391
|
+
const isDeclarationLine = (line) => /^(var|const|type|func|import)\b/.test(line.trim());
|
|
7392
|
+
const declarationLines = inlineLines.filter(isDeclarationLine);
|
|
7393
|
+
const statementLines = inlineLines.filter((line) => !isDeclarationLine(line));
|
|
7394
|
+
const fileInline = [
|
|
7395
|
+
...declarationLines,
|
|
7396
|
+
...(statementLines.length > 0 ? [`func init() {\n\t${statementLines.join('\n\t')}\n}`] : [])
|
|
7397
|
+
].join('\n');
|
|
5940
7398
|
// Computed after all visits: function bodies (outside nodes) register
|
|
5941
7399
|
// packages too, and imports must be captured after those visits.
|
|
5942
7400
|
// Helper-provided packages are only imported here if the file's own code
|
|
@@ -5967,6 +7425,7 @@ function includeLocalImport(code, dir, goFileName) {
|
|
|
5967
7425
|
importedPackages.clear();
|
|
5968
7426
|
for (const p of savedPackages)
|
|
5969
7427
|
importedPackages.add(p);
|
|
7428
|
+
currentFileIsTsx = prevTsx;
|
|
5970
7429
|
for (const p of helperProvidedPackages)
|
|
5971
7430
|
importedPackages.add(p);
|
|
5972
7431
|
currentFileDir = prevDir;
|
|
@@ -6029,7 +7488,13 @@ function needPkg(pkg) {
|
|
|
6029
7488
|
// since the import list is serialized before helpers are emitted.
|
|
6030
7489
|
const helperRequires = {
|
|
6031
7490
|
objectAssign: ['orderedMap'],
|
|
6032
|
-
promiseAll: ['orderedMap', 'asyncWait'],
|
|
7491
|
+
promiseAll: ['orderedMap', 'asyncWait', 'promise'],
|
|
7492
|
+
promise: ['error'],
|
|
7493
|
+
decorators: [],
|
|
7494
|
+
jsx: [],
|
|
7495
|
+
objectIntegrity: [],
|
|
7496
|
+
structuredClone: [],
|
|
7497
|
+
globalThis: ['dynamic'],
|
|
6033
7498
|
namedGroups: ['orderedMap'],
|
|
6034
7499
|
fancyRegex: ['orderedMap'],
|
|
6035
7500
|
dynamic: ['error'],
|
|
@@ -6205,7 +7670,7 @@ const helperPackages = {
|
|
|
6205
7670
|
jsonStringify: ['encoding/json', 'bytes', 'strings', 'unsafe'],
|
|
6206
7671
|
objectAssign: ['reflect', 'unsafe'],
|
|
6207
7672
|
typeOf: ['reflect'],
|
|
6208
|
-
dynamic: ['math', 'strings', 'fmt', 'sort', 'reflect', 'strconv', 'regexp'],
|
|
7673
|
+
dynamic: ['math', 'strings', 'fmt', 'sort', 'reflect', 'strconv', 'regexp', 'unsafe'],
|
|
6209
7674
|
regexReplaceFunc: ['regexp'],
|
|
6210
7675
|
readFile: ['os'],
|
|
6211
7676
|
writeFile: ['os'],
|
|
@@ -6225,7 +7690,14 @@ const helperPackages = {
|
|
|
6225
7690
|
moduleUrl: ['os'],
|
|
6226
7691
|
asyncWait: ['sync'],
|
|
6227
7692
|
timerWait: ['sync', 'time'],
|
|
7693
|
+
uri: ['strings', 'fmt', 'net/url'],
|
|
6228
7694
|
promiseAll: [],
|
|
7695
|
+
promise: ['sync'],
|
|
7696
|
+
decorators: [],
|
|
7697
|
+
jsx: ['strings', 'html', 'reflect', 'strconv', 'fmt'],
|
|
7698
|
+
objectIntegrity: ['sync', 'reflect'],
|
|
7699
|
+
structuredClone: ['reflect', 'unsafe'],
|
|
7700
|
+
globalThis: [],
|
|
6229
7701
|
exec: ['os/exec', 'bytes', 'runtime'],
|
|
6230
7702
|
execSync: ['os/exec', 'bytes', 'runtime'],
|
|
6231
7703
|
runInherit: ['os/exec', 'os'],
|
|
@@ -6256,6 +7728,18 @@ func TnGet(obj interface{}, key string) interface{} {
|
|
|
6256
7728
|
return o.message
|
|
6257
7729
|
}
|
|
6258
7730
|
}
|
|
7731
|
+
rv := reflect.ValueOf(obj)
|
|
7732
|
+
if rv.Kind() == reflect.Pointer && !rv.IsNil() && rv.Elem().Kind() == reflect.Struct {
|
|
7733
|
+
for _, name := range []string{key, key + "_"} {
|
|
7734
|
+
if f := rv.Elem().FieldByName(name); f.IsValid() {
|
|
7735
|
+
// Unexported field: read it through a pointer to bypass the flag
|
|
7736
|
+
return reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem().Interface()
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
if key == "length" {
|
|
7741
|
+
return TnLength(obj)
|
|
7742
|
+
}
|
|
6259
7743
|
return nil
|
|
6260
7744
|
}
|
|
6261
7745
|
|
|
@@ -6267,6 +7751,22 @@ func TnSet(obj interface{}, key string, value interface{}) {
|
|
|
6267
7751
|
panic("TnSet: cannot set property " + key)
|
|
6268
7752
|
}
|
|
6269
7753
|
|
|
7754
|
+
func TnSetIndex(obj interface{}, key interface{}, value interface{}) {
|
|
7755
|
+
switch o := obj.(type) {
|
|
7756
|
+
case map[string]interface{}:
|
|
7757
|
+
if k, ok := key.(string); ok {
|
|
7758
|
+
o[k] = value
|
|
7759
|
+
return
|
|
7760
|
+
}
|
|
7761
|
+
case []interface{}:
|
|
7762
|
+
if i, ok := key.(float64); ok && i >= 0 && int(i) < len(o) {
|
|
7763
|
+
o[int(i)] = value
|
|
7764
|
+
return
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
panic(fmt.Sprintf("TnSetIndex: cannot set property %v", key))
|
|
7768
|
+
}
|
|
7769
|
+
|
|
6270
7770
|
func TnIndex(obj interface{}, key interface{}) interface{} {
|
|
6271
7771
|
switch o := obj.(type) {
|
|
6272
7772
|
case map[string]interface{}:
|
|
@@ -6324,6 +7824,9 @@ func TnLength(v interface{}) float64 {
|
|
|
6324
7824
|
case []interface{}:
|
|
6325
7825
|
return float64(len(t))
|
|
6326
7826
|
}
|
|
7827
|
+
if rv := reflect.ValueOf(v); rv.Kind() == reflect.Slice || rv.Kind() == reflect.Map {
|
|
7828
|
+
return float64(rv.Len())
|
|
7829
|
+
}
|
|
6327
7830
|
return 0
|
|
6328
7831
|
}
|
|
6329
7832
|
|
|
@@ -6466,6 +7969,19 @@ func TnDelete(obj interface{}, key interface{}) bool {
|
|
|
6466
7969
|
return true
|
|
6467
7970
|
}
|
|
6468
7971
|
|
|
7972
|
+
// Object.fromEntries: [key, value] pairs → a dynamic object
|
|
7973
|
+
func TnFromEntries(entries interface{}) interface{} {
|
|
7974
|
+
result := map[string]interface{}{}
|
|
7975
|
+
list := reflect.ValueOf(entries)
|
|
7976
|
+
for i := 0; i < list.Len(); i++ {
|
|
7977
|
+
pair := reflect.ValueOf(list.Index(i).Interface())
|
|
7978
|
+
if pair.Kind() == reflect.Slice && pair.Len() >= 2 {
|
|
7979
|
+
result[fmt.Sprint(pair.Index(0).Interface())] = pair.Index(1).Interface()
|
|
7980
|
+
}
|
|
7981
|
+
}
|
|
7982
|
+
return result
|
|
7983
|
+
}
|
|
7984
|
+
|
|
6469
7985
|
func TnHas(obj interface{}, key interface{}) bool {
|
|
6470
7986
|
if o, ok := obj.(map[string]interface{}); ok {
|
|
6471
7987
|
if k, ok := key.(string); ok {
|
|
@@ -6620,14 +8136,30 @@ func TnIsNil(v interface{}) bool {
|
|
|
6620
8136
|
return false
|
|
6621
8137
|
}`,
|
|
6622
8138
|
typeOf: `func TnTypeOf(v interface{}) string {
|
|
6623
|
-
|
|
8139
|
+
// an unset nullable primitive (*T nil) is undefined
|
|
8140
|
+
switch t := v.(type) {
|
|
6624
8141
|
case nil:
|
|
6625
8142
|
return "undefined"
|
|
6626
|
-
case string
|
|
8143
|
+
case string:
|
|
8144
|
+
return "string"
|
|
8145
|
+
case *string:
|
|
8146
|
+
if t == nil {
|
|
8147
|
+
return "undefined"
|
|
8148
|
+
}
|
|
6627
8149
|
return "string"
|
|
6628
|
-
case float64
|
|
8150
|
+
case float64:
|
|
6629
8151
|
return "number"
|
|
6630
|
-
case
|
|
8152
|
+
case *float64:
|
|
8153
|
+
if t == nil {
|
|
8154
|
+
return "undefined"
|
|
8155
|
+
}
|
|
8156
|
+
return "number"
|
|
8157
|
+
case bool:
|
|
8158
|
+
return "boolean"
|
|
8159
|
+
case *bool:
|
|
8160
|
+
if t == nil {
|
|
8161
|
+
return "undefined"
|
|
8162
|
+
}
|
|
6631
8163
|
return "boolean"
|
|
6632
8164
|
}
|
|
6633
8165
|
if reflect.ValueOf(v).Kind() == reflect.Func {
|
|
@@ -6673,7 +8205,7 @@ func tnJSONWrite(buf *bytes.Buffer, rv reflect.Value, indent string, depth int)
|
|
|
6673
8205
|
case reflect.Float32, reflect.Float64:
|
|
6674
8206
|
buf.WriteString(TnNumStr(rv.Float()))
|
|
6675
8207
|
case reflect.String:
|
|
6676
|
-
data, _ :=
|
|
8208
|
+
data, _ := tnJSONMarshal(rv.String())
|
|
6677
8209
|
buf.Write(data)
|
|
6678
8210
|
case reflect.Slice, reflect.Array:
|
|
6679
8211
|
if rv.Kind() == reflect.Slice && rv.IsNil() {
|
|
@@ -6704,7 +8236,7 @@ func tnJSONWrite(buf *bytes.Buffer, rv reflect.Value, indent string, depth int)
|
|
|
6704
8236
|
buf.WriteByte(',')
|
|
6705
8237
|
tnJSONNewline(buf, indent, depth+1)
|
|
6706
8238
|
}
|
|
6707
|
-
kd, _ :=
|
|
8239
|
+
kd, _ := tnJSONMarshal(ks)
|
|
6708
8240
|
buf.Write(kd)
|
|
6709
8241
|
buf.WriteByte(':')
|
|
6710
8242
|
tnJSONWrite(buf, byKey[ks], indent, depth+1)
|
|
@@ -6739,20 +8271,31 @@ func tnJSONWrite(buf *bytes.Buffer, rv reflect.Value, indent string, depth int)
|
|
|
6739
8271
|
tnJSONNewline(buf, indent, depth+1)
|
|
6740
8272
|
}
|
|
6741
8273
|
first = false
|
|
6742
|
-
kd, _ :=
|
|
8274
|
+
kd, _ := tnJSONMarshal(f.Name)
|
|
6743
8275
|
buf.Write(kd)
|
|
6744
8276
|
buf.WriteByte(':')
|
|
6745
8277
|
tnJSONWrite(buf, value, indent, depth+1)
|
|
6746
8278
|
}
|
|
6747
8279
|
buf.WriteByte('}')
|
|
6748
8280
|
default:
|
|
6749
|
-
data, err :=
|
|
8281
|
+
data, err := tnJSONMarshal(rv.Interface())
|
|
6750
8282
|
if err != nil {
|
|
6751
8283
|
buf.WriteString("null")
|
|
6752
8284
|
return
|
|
6753
8285
|
}
|
|
6754
8286
|
buf.Write(data)
|
|
6755
8287
|
}
|
|
8288
|
+
}
|
|
8289
|
+
|
|
8290
|
+
// json.Marshal without HTML escaping of <, > and & (JSON.stringify does not escape them)
|
|
8291
|
+
func tnJSONMarshal(v interface{}) ([]byte, error) {
|
|
8292
|
+
var buf bytes.Buffer
|
|
8293
|
+
enc := json.NewEncoder(&buf)
|
|
8294
|
+
enc.SetEscapeHTML(false)
|
|
8295
|
+
if err := enc.Encode(v); err != nil {
|
|
8296
|
+
return nil, err
|
|
8297
|
+
}
|
|
8298
|
+
return bytes.TrimSuffix(buf.Bytes(), []byte("\\n")), nil
|
|
6756
8299
|
}`,
|
|
6757
8300
|
orderedMap: `// TnMap: JS Map/Set/object semantics — keys iterate in insertion order
|
|
6758
8301
|
type TnMap[K comparable, V any] struct {
|
|
@@ -6811,6 +8354,51 @@ func (m *TnMap[K, V]) Delete(k K) bool {
|
|
|
6811
8354
|
return true
|
|
6812
8355
|
}
|
|
6813
8356
|
|
|
8357
|
+
// Set operations (ES2025), in the receiver's insertion order then the argument's
|
|
8358
|
+
func (m *TnMap[K, V]) Union(o *TnMap[K, V]) *TnMap[K, V] {
|
|
8359
|
+
r := TnNewMap[K, V]()
|
|
8360
|
+
for _, k := range m.keys {
|
|
8361
|
+
r.Set(k, m.values[k])
|
|
8362
|
+
}
|
|
8363
|
+
for _, k := range o.keys {
|
|
8364
|
+
r.Set(k, o.values[k])
|
|
8365
|
+
}
|
|
8366
|
+
return r
|
|
8367
|
+
}
|
|
8368
|
+
|
|
8369
|
+
func (m *TnMap[K, V]) Intersection(o *TnMap[K, V]) *TnMap[K, V] {
|
|
8370
|
+
r := TnNewMap[K, V]()
|
|
8371
|
+
for _, k := range m.keys {
|
|
8372
|
+
if o.Has(k) {
|
|
8373
|
+
r.Set(k, m.values[k])
|
|
8374
|
+
}
|
|
8375
|
+
}
|
|
8376
|
+
return r
|
|
8377
|
+
}
|
|
8378
|
+
|
|
8379
|
+
func (m *TnMap[K, V]) Difference(o *TnMap[K, V]) *TnMap[K, V] {
|
|
8380
|
+
r := TnNewMap[K, V]()
|
|
8381
|
+
for _, k := range m.keys {
|
|
8382
|
+
if !o.Has(k) {
|
|
8383
|
+
r.Set(k, m.values[k])
|
|
8384
|
+
}
|
|
8385
|
+
}
|
|
8386
|
+
return r
|
|
8387
|
+
}
|
|
8388
|
+
|
|
8389
|
+
func (m *TnMap[K, V]) SymmetricDifference(o *TnMap[K, V]) *TnMap[K, V] {
|
|
8390
|
+
return m.Difference(o).Union(o.Difference(m))
|
|
8391
|
+
}
|
|
8392
|
+
|
|
8393
|
+
func (m *TnMap[K, V]) IsSubsetOf(o *TnMap[K, V]) bool {
|
|
8394
|
+
for _, k := range m.keys {
|
|
8395
|
+
if !o.Has(k) {
|
|
8396
|
+
return false
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
return true
|
|
8400
|
+
}
|
|
8401
|
+
|
|
6814
8402
|
func (m *TnMap[K, V]) Clear() {
|
|
6815
8403
|
m.keys = nil
|
|
6816
8404
|
m.values = map[K]V{}
|
|
@@ -6910,15 +8498,44 @@ func TnRegexExecNamed(re *regexp.Regexp, s string) *TnMatch {
|
|
|
6910
8498
|
type TnRegex struct {
|
|
6911
8499
|
re *regexp.Regexp
|
|
6912
8500
|
bt *tnBt
|
|
6913
|
-
lastIndex
|
|
8501
|
+
lastIndex float64
|
|
6914
8502
|
global bool
|
|
8503
|
+
sticky bool
|
|
8504
|
+
source string
|
|
8505
|
+
flags string
|
|
8506
|
+
}
|
|
8507
|
+
|
|
8508
|
+
func TnRegexCompile(pattern string, flags string, source string) *TnRegex {
|
|
8509
|
+
r := &TnRegex{global: strings.Contains(flags, "g"), sticky: strings.Contains(flags, "y"), source: source, flags: flags}
|
|
8510
|
+
prefix := ""
|
|
8511
|
+
for _, f := range "ims" {
|
|
8512
|
+
if strings.ContainsRune(flags, f) {
|
|
8513
|
+
prefix += string(f)
|
|
8514
|
+
}
|
|
8515
|
+
}
|
|
8516
|
+
if prefix != "" {
|
|
8517
|
+
prefix = "(?" + prefix + ")"
|
|
8518
|
+
}
|
|
8519
|
+
if re, err := regexp.Compile(prefix + pattern); err == nil {
|
|
8520
|
+
r.re = re
|
|
8521
|
+
} else {
|
|
8522
|
+
r.bt = tnBtCompile(pattern)
|
|
8523
|
+
}
|
|
8524
|
+
return r
|
|
8525
|
+
}
|
|
8526
|
+
|
|
8527
|
+
// .source / .flags of a plain RE2 regex: flags lead the pattern as (?ims)
|
|
8528
|
+
var tnRegexpFlagPrefix = regexp.MustCompile("^\\\\(\\\\?([ims]+)\\\\)")
|
|
8529
|
+
|
|
8530
|
+
func TnRegexpSource(re *regexp.Regexp) string {
|
|
8531
|
+
return strings.ReplaceAll(tnRegexpFlagPrefix.ReplaceAllString(re.String(), ""), "(?P<", "(?<")
|
|
6915
8532
|
}
|
|
6916
8533
|
|
|
6917
|
-
func
|
|
6918
|
-
if
|
|
6919
|
-
return
|
|
8534
|
+
func TnRegexpFlags(re *regexp.Regexp) string {
|
|
8535
|
+
if m := tnRegexpFlagPrefix.FindStringSubmatch(re.String()); m != nil {
|
|
8536
|
+
return m[1]
|
|
6920
8537
|
}
|
|
6921
|
-
return
|
|
8538
|
+
return ""
|
|
6922
8539
|
}
|
|
6923
8540
|
|
|
6924
8541
|
func (r *TnRegex) Test(s string) bool {
|
|
@@ -7065,15 +8682,15 @@ func (r *TnRegex) find(s string) ([]string, []int) {
|
|
|
7065
8682
|
runs := []rune(s)
|
|
7066
8683
|
if r.bt != nil {
|
|
7067
8684
|
start := 0
|
|
7068
|
-
if r.global {
|
|
7069
|
-
if r.lastIndex > len(runs) {
|
|
8685
|
+
if r.global || r.sticky {
|
|
8686
|
+
if int(r.lastIndex) > len(runs) {
|
|
7070
8687
|
r.lastIndex = 0
|
|
7071
8688
|
return nil, nil
|
|
7072
8689
|
}
|
|
7073
|
-
start = r.lastIndex
|
|
8690
|
+
start = int(r.lastIndex)
|
|
7074
8691
|
}
|
|
7075
8692
|
offs := tnByteOffsets(runs)
|
|
7076
|
-
for pos := start; pos <= len(runs); pos++ {
|
|
8693
|
+
for pos := start; pos <= len(runs) && (!r.sticky || pos == start); pos++ {
|
|
7077
8694
|
caps := make([]int, (r.bt.ngroups+1)*2)
|
|
7078
8695
|
for i := range caps {
|
|
7079
8696
|
caps[i] = -1
|
|
@@ -7093,22 +8710,22 @@ func (r *TnRegex) find(s string) ([]string, []int) {
|
|
|
7093
8710
|
loc[2*i], loc[2*i+1] = -1, -1
|
|
7094
8711
|
}
|
|
7095
8712
|
}
|
|
7096
|
-
if r.global {
|
|
8713
|
+
if r.global || r.sticky {
|
|
7097
8714
|
next := end
|
|
7098
8715
|
if next == pos {
|
|
7099
8716
|
next++
|
|
7100
8717
|
}
|
|
7101
|
-
r.lastIndex = next
|
|
8718
|
+
r.lastIndex = float64(next)
|
|
7102
8719
|
}
|
|
7103
8720
|
return groups, loc
|
|
7104
8721
|
}
|
|
7105
8722
|
}
|
|
7106
|
-
if r.global {
|
|
8723
|
+
if r.global || r.sticky {
|
|
7107
8724
|
r.lastIndex = 0
|
|
7108
8725
|
}
|
|
7109
8726
|
return nil, nil
|
|
7110
8727
|
}
|
|
7111
|
-
if !r.global {
|
|
8728
|
+
if !r.global && !r.sticky {
|
|
7112
8729
|
loc := r.re.FindStringSubmatchIndex(s)
|
|
7113
8730
|
if loc == nil {
|
|
7114
8731
|
return nil, nil
|
|
@@ -7121,16 +8738,16 @@ func (r *TnRegex) find(s string) ([]string, []int) {
|
|
|
7121
8738
|
}
|
|
7122
8739
|
return groups, loc
|
|
7123
8740
|
}
|
|
7124
|
-
if r.lastIndex >= len(runs) {
|
|
8741
|
+
if int(r.lastIndex) >= len(runs) {
|
|
7125
8742
|
r.lastIndex = 0
|
|
7126
8743
|
return nil, nil
|
|
7127
8744
|
}
|
|
7128
8745
|
byteStart := 0
|
|
7129
|
-
for i := 0; i < r.lastIndex; i++ {
|
|
8746
|
+
for i := 0; i < int(r.lastIndex); i++ {
|
|
7130
8747
|
byteStart += len(string(runs[i]))
|
|
7131
8748
|
}
|
|
7132
8749
|
loc := r.re.FindStringSubmatchIndex(s[byteStart:])
|
|
7133
|
-
if loc == nil {
|
|
8750
|
+
if loc == nil || (r.sticky && loc[0] != 0) {
|
|
7134
8751
|
r.lastIndex = 0
|
|
7135
8752
|
return nil, nil
|
|
7136
8753
|
}
|
|
@@ -7145,16 +8762,16 @@ func (r *TnRegex) find(s string) ([]string, []int) {
|
|
|
7145
8762
|
groups[i] = s[loc[2*i]:loc[2*i+1]]
|
|
7146
8763
|
}
|
|
7147
8764
|
}
|
|
7148
|
-
endRune := r.lastIndex
|
|
8765
|
+
endRune := int(r.lastIndex)
|
|
7149
8766
|
bo := byteStart
|
|
7150
|
-
for ri := r.lastIndex; ri < len(runs) && bo < loc[1]; ri++ {
|
|
8767
|
+
for ri := int(r.lastIndex); ri < len(runs) && bo < loc[1]; ri++ {
|
|
7151
8768
|
bo += len(string(runs[ri]))
|
|
7152
8769
|
endRune = ri + 1
|
|
7153
8770
|
}
|
|
7154
|
-
if endRune == r.lastIndex {
|
|
8771
|
+
if endRune == int(r.lastIndex) {
|
|
7155
8772
|
endRune++
|
|
7156
8773
|
}
|
|
7157
|
-
r.lastIndex = endRune
|
|
8774
|
+
r.lastIndex = float64(endRune)
|
|
7158
8775
|
return groups, loc
|
|
7159
8776
|
}
|
|
7160
8777
|
|
|
@@ -7926,14 +9543,480 @@ func TnNewSymbol(description string) *TnSymbol {
|
|
|
7926
9543
|
return &TnSymbol{description: description}
|
|
7927
9544
|
}`,
|
|
7928
9545
|
timerWait: `// Pending setTimeout timers are waited on before the process exits
|
|
7929
|
-
var __tnTimers sync.WaitGroup
|
|
9546
|
+
var __tnTimers sync.WaitGroup
|
|
9547
|
+
|
|
9548
|
+
func TnSetTimeout(ms float64, fn func()) *time.Timer {
|
|
9549
|
+
__tnTimers.Add(1)
|
|
9550
|
+
return time.AfterFunc(time.Duration(ms*float64(time.Millisecond)), func() {
|
|
9551
|
+
defer __tnTimers.Done()
|
|
9552
|
+
fn()
|
|
9553
|
+
TnRunMicrotasks()
|
|
9554
|
+
})
|
|
9555
|
+
}
|
|
9556
|
+
|
|
9557
|
+
// setInterval: fn every ms until cleared (the process lives until then)
|
|
9558
|
+
type TnInterval struct {
|
|
9559
|
+
stop chan struct{}
|
|
9560
|
+
once sync.Once
|
|
9561
|
+
}
|
|
9562
|
+
|
|
9563
|
+
func TnSetInterval(ms float64, fn func()) *TnInterval {
|
|
9564
|
+
__tnTimers.Add(1)
|
|
9565
|
+
t := &TnInterval{stop: make(chan struct{})}
|
|
9566
|
+
go func() {
|
|
9567
|
+
ticker := time.NewTicker(max(time.Duration(ms*float64(time.Millisecond)), time.Millisecond))
|
|
9568
|
+
defer ticker.Stop()
|
|
9569
|
+
for {
|
|
9570
|
+
select {
|
|
9571
|
+
case <-t.stop:
|
|
9572
|
+
return
|
|
9573
|
+
case <-ticker.C:
|
|
9574
|
+
fn()
|
|
9575
|
+
TnRunMicrotasks()
|
|
9576
|
+
}
|
|
9577
|
+
}
|
|
9578
|
+
}()
|
|
9579
|
+
return t
|
|
9580
|
+
}
|
|
9581
|
+
|
|
9582
|
+
func TnClearInterval(t *TnInterval) {
|
|
9583
|
+
if t != nil {
|
|
9584
|
+
t.once.Do(func() {
|
|
9585
|
+
close(t.stop)
|
|
9586
|
+
__tnTimers.Done()
|
|
9587
|
+
})
|
|
9588
|
+
}
|
|
9589
|
+
}
|
|
9590
|
+
|
|
9591
|
+
// queueMicrotask: run after the current synchronous code (end of main or of a timer callback)
|
|
9592
|
+
var tnMicrotasks []func()
|
|
9593
|
+
var tnMicrotasksMu sync.Mutex
|
|
9594
|
+
|
|
9595
|
+
func TnQueueMicrotask(fn func()) {
|
|
9596
|
+
tnMicrotasksMu.Lock()
|
|
9597
|
+
tnMicrotasks = append(tnMicrotasks, fn)
|
|
9598
|
+
tnMicrotasksMu.Unlock()
|
|
9599
|
+
}
|
|
9600
|
+
|
|
9601
|
+
func TnRunMicrotasks() {
|
|
9602
|
+
for {
|
|
9603
|
+
tnMicrotasksMu.Lock()
|
|
9604
|
+
if len(tnMicrotasks) == 0 {
|
|
9605
|
+
tnMicrotasksMu.Unlock()
|
|
9606
|
+
return
|
|
9607
|
+
}
|
|
9608
|
+
fn := tnMicrotasks[0]
|
|
9609
|
+
tnMicrotasks = tnMicrotasks[1:]
|
|
9610
|
+
tnMicrotasksMu.Unlock()
|
|
9611
|
+
fn()
|
|
9612
|
+
}
|
|
9613
|
+
}
|
|
9614
|
+
|
|
9615
|
+
func TnClearTimeout(t *time.Timer) {
|
|
9616
|
+
if t != nil && t.Stop() {
|
|
9617
|
+
__tnTimers.Done()
|
|
9618
|
+
}
|
|
9619
|
+
}`,
|
|
9620
|
+
uri: `// encodeURIComponent: escapes everything except A-Z a-z 0-9 - _ . ! ~ * ' ( )
|
|
9621
|
+
func TnEncodeURIComponent(s string) string {
|
|
9622
|
+
var b strings.Builder
|
|
9623
|
+
for _, c := range []byte(s) {
|
|
9624
|
+
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || strings.IndexByte("-_.!~*'()", c) >= 0 {
|
|
9625
|
+
b.WriteByte(c)
|
|
9626
|
+
} else {
|
|
9627
|
+
fmt.Fprintf(&b, "%%%02X", c)
|
|
9628
|
+
}
|
|
9629
|
+
}
|
|
9630
|
+
return b.String()
|
|
9631
|
+
}
|
|
9632
|
+
|
|
9633
|
+
func TnDecodeURIComponent(s string) string {
|
|
9634
|
+
decoded, err := tnurl.PathUnescape(s)
|
|
9635
|
+
if err != nil {
|
|
9636
|
+
panic(err)
|
|
9637
|
+
}
|
|
9638
|
+
return decoded
|
|
9639
|
+
}`,
|
|
9640
|
+
objectIntegrity: `// Object.freeze / seal / preventExtensions levels, by object identity
|
|
9641
|
+
var tnIntegrity sync.Map
|
|
9642
|
+
|
|
9643
|
+
func tnIdentity(v interface{}) (uintptr, bool) {
|
|
9644
|
+
rv := reflect.ValueOf(v)
|
|
9645
|
+
switch rv.Kind() {
|
|
9646
|
+
case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Func, reflect.Chan:
|
|
9647
|
+
return rv.Pointer(), true
|
|
9648
|
+
}
|
|
9649
|
+
return 0, false
|
|
9650
|
+
}
|
|
9651
|
+
|
|
9652
|
+
func TnSetIntegrity[T any](v T, level int) T {
|
|
9653
|
+
if id, ok := tnIdentity(v); ok {
|
|
9654
|
+
if prev, had := tnIntegrity.Load(id); !had || prev.(int) < level {
|
|
9655
|
+
tnIntegrity.Store(id, level)
|
|
9656
|
+
}
|
|
9657
|
+
}
|
|
9658
|
+
return v
|
|
9659
|
+
}
|
|
9660
|
+
|
|
9661
|
+
// Primitives are always frozen
|
|
9662
|
+
func TnHasIntegrity(v interface{}, level int) bool {
|
|
9663
|
+
id, ok := tnIdentity(v)
|
|
9664
|
+
if !ok {
|
|
9665
|
+
return true
|
|
9666
|
+
}
|
|
9667
|
+
l, had := tnIntegrity.Load(id)
|
|
9668
|
+
return had && l.(int) >= level
|
|
9669
|
+
}`,
|
|
9670
|
+
structuredClone: `// structuredClone: a deep copy (pointers, structs, slices, maps)
|
|
9671
|
+
func TnStructuredClone[T any](v T) T {
|
|
9672
|
+
return tnCloneValue(reflect.ValueOf(&v).Elem()).Interface().(T)
|
|
9673
|
+
}
|
|
9674
|
+
|
|
9675
|
+
func tnCloneValue(v reflect.Value) reflect.Value {
|
|
9676
|
+
switch v.Kind() {
|
|
9677
|
+
case reflect.Pointer:
|
|
9678
|
+
if v.IsNil() {
|
|
9679
|
+
return v
|
|
9680
|
+
}
|
|
9681
|
+
c := reflect.New(v.Elem().Type())
|
|
9682
|
+
c.Elem().Set(tnCloneValue(v.Elem()))
|
|
9683
|
+
return c
|
|
9684
|
+
case reflect.Struct:
|
|
9685
|
+
if !v.CanAddr() {
|
|
9686
|
+
tmp := reflect.New(v.Type()).Elem()
|
|
9687
|
+
tmp.Set(v)
|
|
9688
|
+
v = tmp
|
|
9689
|
+
}
|
|
9690
|
+
c := reflect.New(v.Type()).Elem()
|
|
9691
|
+
for i := 0; i < v.NumField(); i++ {
|
|
9692
|
+
// unexported fields are read and written through their addresses
|
|
9693
|
+
src := reflect.NewAt(v.Field(i).Type(), unsafe.Pointer(v.Field(i).UnsafeAddr())).Elem()
|
|
9694
|
+
dst := reflect.NewAt(c.Field(i).Type(), unsafe.Pointer(c.Field(i).UnsafeAddr())).Elem()
|
|
9695
|
+
dst.Set(tnCloneValue(src))
|
|
9696
|
+
}
|
|
9697
|
+
return c
|
|
9698
|
+
case reflect.Slice:
|
|
9699
|
+
if v.IsNil() {
|
|
9700
|
+
return v
|
|
9701
|
+
}
|
|
9702
|
+
c := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
|
9703
|
+
for i := 0; i < v.Len(); i++ {
|
|
9704
|
+
c.Index(i).Set(tnCloneValue(v.Index(i)))
|
|
9705
|
+
}
|
|
9706
|
+
return c
|
|
9707
|
+
case reflect.Map:
|
|
9708
|
+
if v.IsNil() {
|
|
9709
|
+
return v
|
|
9710
|
+
}
|
|
9711
|
+
c := reflect.MakeMapWithSize(v.Type(), v.Len())
|
|
9712
|
+
iter := v.MapRange()
|
|
9713
|
+
for iter.Next() {
|
|
9714
|
+
c.SetMapIndex(iter.Key(), tnCloneValue(iter.Value()))
|
|
9715
|
+
}
|
|
9716
|
+
return c
|
|
9717
|
+
case reflect.Interface:
|
|
9718
|
+
if v.IsNil() {
|
|
9719
|
+
return v
|
|
9720
|
+
}
|
|
9721
|
+
c := reflect.New(v.Type()).Elem()
|
|
9722
|
+
c.Set(tnCloneValue(v.Elem()))
|
|
9723
|
+
return c
|
|
9724
|
+
}
|
|
9725
|
+
return v
|
|
9726
|
+
}`,
|
|
9727
|
+
globalThis: `// (globalThis as any).x: properties set on the global object at runtime
|
|
9728
|
+
var TnGlobalThis = map[string]interface{}{}`,
|
|
9729
|
+
jsx: `// JSX renders to HTML: TnJsxNode is markup, plain strings are escaped text
|
|
9730
|
+
type TnJsxNode string
|
|
9731
|
+
|
|
9732
|
+
type TnJsxAttr struct {
|
|
9733
|
+
name string
|
|
9734
|
+
value interface{}
|
|
9735
|
+
}
|
|
9736
|
+
|
|
9737
|
+
var tnJsxVoidTags = map[string]bool{"area": true, "base": true, "br": true, "col": true, "embed": true, "hr": true, "img": true, "input": true, "link": true, "meta": true, "source": true, "track": true, "wbr": true}
|
|
9738
|
+
|
|
9739
|
+
func TnJsxElement(tag string, attrs []TnJsxAttr, children []interface{}) TnJsxNode {
|
|
9740
|
+
var b strings.Builder
|
|
9741
|
+
b.WriteString("<" + tag)
|
|
9742
|
+
for _, a := range attrs {
|
|
9743
|
+
name := a.name
|
|
9744
|
+
switch name {
|
|
9745
|
+
case "key", "ref":
|
|
9746
|
+
continue
|
|
9747
|
+
case "className":
|
|
9748
|
+
name = "class"
|
|
9749
|
+
case "htmlFor":
|
|
9750
|
+
name = "for"
|
|
9751
|
+
}
|
|
9752
|
+
if a.value == nil {
|
|
9753
|
+
continue
|
|
9754
|
+
}
|
|
9755
|
+
if v, ok := a.value.(bool); ok {
|
|
9756
|
+
if v {
|
|
9757
|
+
b.WriteString(" " + name)
|
|
9758
|
+
}
|
|
9759
|
+
continue
|
|
9760
|
+
}
|
|
9761
|
+
if reflect.ValueOf(a.value).Kind() == reflect.Func {
|
|
9762
|
+
continue
|
|
9763
|
+
}
|
|
9764
|
+
b.WriteString(" " + name + "=\\"" + html.EscapeString(tnJsxString(a.value)) + "\\"")
|
|
9765
|
+
}
|
|
9766
|
+
if tnJsxVoidTags[tag] {
|
|
9767
|
+
b.WriteString("/>")
|
|
9768
|
+
return TnJsxNode(b.String())
|
|
9769
|
+
}
|
|
9770
|
+
b.WriteString(">")
|
|
9771
|
+
b.WriteString(string(TnJsxChildren(children)))
|
|
9772
|
+
b.WriteString("</" + tag + ">")
|
|
9773
|
+
return TnJsxNode(b.String())
|
|
9774
|
+
}
|
|
9775
|
+
|
|
9776
|
+
func TnJsxChildren(children []interface{}) TnJsxNode {
|
|
9777
|
+
var b strings.Builder
|
|
9778
|
+
for _, c := range children {
|
|
9779
|
+
tnJsxWriteChild(&b, c)
|
|
9780
|
+
}
|
|
9781
|
+
return TnJsxNode(b.String())
|
|
9782
|
+
}
|
|
9783
|
+
|
|
9784
|
+
// null / booleans render nothing; arrays render each item
|
|
9785
|
+
func tnJsxWriteChild(b *strings.Builder, c interface{}) {
|
|
9786
|
+
switch v := c.(type) {
|
|
9787
|
+
case nil, bool:
|
|
9788
|
+
return
|
|
9789
|
+
case TnJsxNode:
|
|
9790
|
+
b.WriteString(string(v))
|
|
9791
|
+
return
|
|
9792
|
+
case string:
|
|
9793
|
+
b.WriteString(html.EscapeString(v))
|
|
9794
|
+
return
|
|
9795
|
+
}
|
|
9796
|
+
rv := reflect.ValueOf(c)
|
|
9797
|
+
switch rv.Kind() {
|
|
9798
|
+
case reflect.Slice:
|
|
9799
|
+
for i := 0; i < rv.Len(); i++ {
|
|
9800
|
+
tnJsxWriteChild(b, rv.Index(i).Interface())
|
|
9801
|
+
}
|
|
9802
|
+
return
|
|
9803
|
+
case reflect.Pointer:
|
|
9804
|
+
if !rv.IsNil() {
|
|
9805
|
+
tnJsxWriteChild(b, rv.Elem().Interface())
|
|
9806
|
+
}
|
|
9807
|
+
return
|
|
9808
|
+
}
|
|
9809
|
+
b.WriteString(html.EscapeString(tnJsxString(c)))
|
|
9810
|
+
}
|
|
9811
|
+
|
|
9812
|
+
func tnJsxString(v interface{}) string {
|
|
9813
|
+
switch t := v.(type) {
|
|
9814
|
+
case string:
|
|
9815
|
+
return t
|
|
9816
|
+
case TnJsxNode:
|
|
9817
|
+
return string(t)
|
|
9818
|
+
case float64:
|
|
9819
|
+
return strconv.FormatFloat(t, 'f', -1, 64)
|
|
9820
|
+
}
|
|
9821
|
+
return fmt.Sprint(v)
|
|
9822
|
+
}`,
|
|
9823
|
+
decorators: `// this inside plain functions: the receiver of the decorated method being called
|
|
9824
|
+
var TnThis interface{}
|
|
9825
|
+
|
|
9826
|
+
// The context a decorator receives (kind is "method" or "class")
|
|
9827
|
+
type TnDecoratorContext struct {
|
|
9828
|
+
kind string
|
|
9829
|
+
name string
|
|
9830
|
+
static bool
|
|
9831
|
+
private bool
|
|
9832
|
+
}
|
|
9833
|
+
|
|
9834
|
+
// A method decorator's result replaces the method; undefined keeps it
|
|
9835
|
+
func TnDecorate(prev func(...interface{}) interface{}, result interface{}) func(...interface{}) interface{} {
|
|
9836
|
+
if result == nil {
|
|
9837
|
+
return prev
|
|
9838
|
+
}
|
|
9839
|
+
if f, ok := result.(func(...interface{}) interface{}); ok && f != nil {
|
|
9840
|
+
return f
|
|
9841
|
+
}
|
|
9842
|
+
return prev
|
|
9843
|
+
}
|
|
9844
|
+
|
|
9845
|
+
// Class decorators may not replace a class (a Go type)
|
|
9846
|
+
func TnDecorateClass(result interface{}) {
|
|
9847
|
+
if result != nil {
|
|
9848
|
+
panic("class decorators cannot replace the class")
|
|
9849
|
+
}
|
|
9850
|
+
}`,
|
|
9851
|
+
promise: `// Promises are channels; a rejected promise is a closed channel with a reason
|
|
9852
|
+
var tnRejections sync.Map
|
|
9853
|
+
|
|
9854
|
+
func TnReject[T any](ch chan T, reason interface{}) {
|
|
9855
|
+
tnRejections.Store(ch, reason)
|
|
9856
|
+
close(ch)
|
|
9857
|
+
}
|
|
9858
|
+
|
|
9859
|
+
// Deferred in async functions: a throw rejects the function's promise
|
|
9860
|
+
func TnRejectOnPanic[T any](ch chan T) {
|
|
9861
|
+
if r := recover(); r != nil {
|
|
9862
|
+
tnRejections.Store(ch, r)
|
|
9863
|
+
}
|
|
9864
|
+
}
|
|
9865
|
+
|
|
9866
|
+
// Waits for a promise: its value, or its rejection reason
|
|
9867
|
+
func tnSettle[T any](ch chan T) (T, interface{}, bool) {
|
|
9868
|
+
v, ok := <-ch
|
|
9869
|
+
if !ok {
|
|
9870
|
+
if reason, rejected := tnRejections.Load(ch); rejected {
|
|
9871
|
+
return v, reason, true
|
|
9872
|
+
}
|
|
9873
|
+
}
|
|
9874
|
+
return v, nil, false
|
|
9875
|
+
}
|
|
9876
|
+
|
|
9877
|
+
// await: the value, or the rejection rethrown
|
|
9878
|
+
func TnAwait[T any](ch chan T) T {
|
|
9879
|
+
v, reason, rejected := tnSettle(ch)
|
|
9880
|
+
if rejected {
|
|
9881
|
+
panic(reason)
|
|
9882
|
+
}
|
|
9883
|
+
return v
|
|
9884
|
+
}
|
|
9885
|
+
|
|
9886
|
+
func TnPromiseReject[T any](reason interface{}) chan T {
|
|
9887
|
+
ch := make(chan T)
|
|
9888
|
+
TnReject(ch, reason)
|
|
9889
|
+
return ch
|
|
9890
|
+
}
|
|
9891
|
+
|
|
9892
|
+
func TnPromiseCatch[T any, E any](p chan T, onRejected func(E)) chan T {
|
|
9893
|
+
out := make(chan T, 1)
|
|
9894
|
+
go func() {
|
|
9895
|
+
v, reason, rejected := tnSettle(p)
|
|
9896
|
+
if rejected {
|
|
9897
|
+
e, _ := reason.(E)
|
|
9898
|
+
onRejected(e)
|
|
9899
|
+
}
|
|
9900
|
+
out <- v
|
|
9901
|
+
close(out)
|
|
9902
|
+
}()
|
|
9903
|
+
return out
|
|
9904
|
+
}
|
|
9905
|
+
|
|
9906
|
+
func TnPromiseCatchValue[T any, E any](p chan T, onRejected func(E) T) chan T {
|
|
9907
|
+
out := make(chan T, 1)
|
|
9908
|
+
go func() {
|
|
9909
|
+
v, reason, rejected := tnSettle(p)
|
|
9910
|
+
if rejected {
|
|
9911
|
+
e, _ := reason.(E)
|
|
9912
|
+
v = onRejected(e)
|
|
9913
|
+
}
|
|
9914
|
+
out <- v
|
|
9915
|
+
close(out)
|
|
9916
|
+
}()
|
|
9917
|
+
return out
|
|
9918
|
+
}
|
|
9919
|
+
|
|
9920
|
+
func TnPromiseFinally[T any](p chan T, onFinally func()) chan T {
|
|
9921
|
+
out := make(chan T, 1)
|
|
9922
|
+
go func() {
|
|
9923
|
+
v, reason, rejected := tnSettle(p)
|
|
9924
|
+
onFinally()
|
|
9925
|
+
if rejected {
|
|
9926
|
+
TnReject(out, reason)
|
|
9927
|
+
return
|
|
9928
|
+
}
|
|
9929
|
+
out <- v
|
|
9930
|
+
close(out)
|
|
9931
|
+
}()
|
|
9932
|
+
return out
|
|
9933
|
+
}
|
|
9934
|
+
|
|
9935
|
+
// Promise.race: the first promise to settle, fulfilled or rejected
|
|
9936
|
+
func TnPromiseRace[T any](chs []chan T) chan T {
|
|
9937
|
+
out := make(chan T, 1)
|
|
9938
|
+
var once sync.Once
|
|
9939
|
+
for _, ch := range chs {
|
|
9940
|
+
go func(ch chan T) {
|
|
9941
|
+
v, reason, rejected := tnSettle(ch)
|
|
9942
|
+
once.Do(func() {
|
|
9943
|
+
if rejected {
|
|
9944
|
+
TnReject(out, reason)
|
|
9945
|
+
return
|
|
9946
|
+
}
|
|
9947
|
+
out <- v
|
|
9948
|
+
close(out)
|
|
9949
|
+
})
|
|
9950
|
+
}(ch)
|
|
9951
|
+
}
|
|
9952
|
+
return out
|
|
9953
|
+
}
|
|
9954
|
+
|
|
9955
|
+
type TnSettled[T any] struct {
|
|
9956
|
+
status string
|
|
9957
|
+
value T
|
|
9958
|
+
reason interface{}
|
|
9959
|
+
}
|
|
9960
|
+
|
|
9961
|
+
func TnPromiseAllSettled[T any](chs []chan T) chan []*TnSettled[T] {
|
|
9962
|
+
out := make(chan []*TnSettled[T], 1)
|
|
9963
|
+
go func() {
|
|
9964
|
+
results := make([]*TnSettled[T], len(chs))
|
|
9965
|
+
for i, ch := range chs {
|
|
9966
|
+
v, reason, rejected := tnSettle(ch)
|
|
9967
|
+
if rejected {
|
|
9968
|
+
results[i] = &TnSettled[T]{status: "rejected", reason: reason}
|
|
9969
|
+
} else {
|
|
9970
|
+
results[i] = &TnSettled[T]{status: "fulfilled", value: v}
|
|
9971
|
+
}
|
|
9972
|
+
}
|
|
9973
|
+
out <- results
|
|
9974
|
+
close(out)
|
|
9975
|
+
}()
|
|
9976
|
+
return out
|
|
9977
|
+
}
|
|
9978
|
+
|
|
9979
|
+
// Promise.any: the first promise to fulfil; rejected when all of them reject
|
|
9980
|
+
func TnPromiseAny[T any](chs []chan T) chan T {
|
|
9981
|
+
out := make(chan T, 1)
|
|
9982
|
+
go func() {
|
|
9983
|
+
values := make(chan T, len(chs))
|
|
9984
|
+
failures := make(chan interface{}, len(chs))
|
|
9985
|
+
for _, ch := range chs {
|
|
9986
|
+
go func(ch chan T) {
|
|
9987
|
+
v, reason, rejected := tnSettle(ch)
|
|
9988
|
+
if rejected {
|
|
9989
|
+
failures <- reason
|
|
9990
|
+
} else {
|
|
9991
|
+
values <- v
|
|
9992
|
+
}
|
|
9993
|
+
}(ch)
|
|
9994
|
+
}
|
|
9995
|
+
for range chs {
|
|
9996
|
+
select {
|
|
9997
|
+
case v := <-values:
|
|
9998
|
+
out <- v
|
|
9999
|
+
close(out)
|
|
10000
|
+
return
|
|
10001
|
+
case <-failures:
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
TnReject(out, TnNewError("All promises were rejected"))
|
|
10005
|
+
}()
|
|
10006
|
+
return out
|
|
10007
|
+
}`,
|
|
7930
10008
|
promiseAll: `// Promise.all: every channel drained in order, results collected
|
|
7931
10009
|
func TnPromiseAll[T any](chs []chan T) chan []T {
|
|
7932
10010
|
out := make(chan []T)
|
|
7933
10011
|
go func() {
|
|
7934
10012
|
results := make([]T, len(chs))
|
|
7935
10013
|
for i, ch := range chs {
|
|
7936
|
-
|
|
10014
|
+
v, reason, rejected := tnSettle(ch)
|
|
10015
|
+
if rejected {
|
|
10016
|
+
TnReject(out, reason)
|
|
10017
|
+
return
|
|
10018
|
+
}
|
|
10019
|
+
results[i] = v
|
|
7937
10020
|
}
|
|
7938
10021
|
out <- results
|
|
7939
10022
|
close(out)
|
|
@@ -8118,7 +10201,21 @@ function visitImportDeclaration(node) {
|
|
|
8118
10201
|
const resolved = fileResolver(moduleSpec, currentFileDir);
|
|
8119
10202
|
if (resolved) {
|
|
8120
10203
|
const goFileName = specifierToGoFileName(moduleSpec);
|
|
8121
|
-
includeLocalImport(resolved.content, resolved.dir, goFileName);
|
|
10204
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName, !!resolved.tsx);
|
|
10205
|
+
}
|
|
10206
|
+
}
|
|
10207
|
+
// Renamed bindings refer to the original declaration (one Go package)
|
|
10208
|
+
const bindings = node.importClause?.namedBindings;
|
|
10209
|
+
if (bindings && isNamespaceImport(bindings))
|
|
10210
|
+
defaultImportNamespaces.add(bindings.name.text);
|
|
10211
|
+
if (bindings && isNamedImports(bindings)) {
|
|
10212
|
+
for (const el of bindings.elements ?? []) {
|
|
10213
|
+
if (!el.propertyName)
|
|
10214
|
+
continue;
|
|
10215
|
+
importAliases.set(el.name.text, getSafeName(el.propertyName.text));
|
|
10216
|
+
const fn = declaredFunctions.get(el.propertyName.text);
|
|
10217
|
+
if (fn)
|
|
10218
|
+
declaredFunctions.set(el.name.text, fn);
|
|
8122
10219
|
}
|
|
8123
10220
|
}
|
|
8124
10221
|
return '';
|
|
@@ -8166,7 +10263,8 @@ function visitImportDeclaration(node) {
|
|
|
8166
10263
|
// variables (a variable named `big` would shadow the package name)
|
|
8167
10264
|
const goImportAliases = {
|
|
8168
10265
|
'math/big': 'tnbig',
|
|
8169
|
-
'net/url': 'tnurl'
|
|
10266
|
+
'net/url': 'tnurl',
|
|
10267
|
+
'golang.org/x/text/unicode/norm': 'tnnorm'
|
|
8170
10268
|
};
|
|
8171
10269
|
function goImportLine(pkg) {
|
|
8172
10270
|
const alias = goImportAliases[pkg];
|