typenative 0.1.2 → 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 +891 -34
- package/conformance.md +17 -17
- package/package.json +1 -1
- package/tsparser/main.go +37 -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
|
@@ -338,6 +338,8 @@ const interfacePropertyTypes = new Map();
|
|
|
338
338
|
const typeAliases = new Map();
|
|
339
339
|
const enumNames = new Set();
|
|
340
340
|
const enumBaseTypes = new Map();
|
|
341
|
+
// Member types of heterogeneous enums ("E.A" → float64 / string)
|
|
342
|
+
const enumMemberGoTypes = new Map();
|
|
341
343
|
// Maps local TS name → Go qualified name (e.g. 'Println' → 'fmt.Println', 'myFmt' → 'fmt')
|
|
342
344
|
const importAliases = new Map();
|
|
343
345
|
// Tracks static methods per class: Set of "ClassName.methodName" strings
|
|
@@ -390,15 +392,19 @@ const OPERATOR_TEXT = {
|
|
|
390
392
|
function operatorTokenText(token) {
|
|
391
393
|
return OPERATOR_TEXT[token?.kind] ?? token?.kind ?? '';
|
|
392
394
|
}
|
|
393
|
-
function defaultParseFunction(_code) {
|
|
395
|
+
function defaultParseFunction(_code, _tsx) {
|
|
394
396
|
throw new Error('transpileToNative: no parse function injected (options.parse)');
|
|
395
397
|
}
|
|
396
398
|
// Parses via the injected parser and links each node to its parent: the JSON
|
|
397
399
|
// AST carries no parent pointers, but the transpiler walks up via node.parent.
|
|
398
400
|
function parseSource(code) {
|
|
399
|
-
const sourceFile = parseFunction(code);
|
|
401
|
+
const sourceFile = parseFunction(code, currentFileIsTsx);
|
|
400
402
|
stripThisParameters(sourceFile);
|
|
401
403
|
lowerIndexSignatures(sourceFile);
|
|
404
|
+
lowerAmbientDeclarations(sourceFile);
|
|
405
|
+
lowerModuleForms(sourceFile);
|
|
406
|
+
lowerDisposeSymbols(sourceFile);
|
|
407
|
+
lowerNamespaces(sourceFile);
|
|
402
408
|
lowerClassExpressions(sourceFile);
|
|
403
409
|
lowerWeakCollections(sourceFile);
|
|
404
410
|
lowerGlobalThis(sourceFile);
|
|
@@ -441,6 +447,26 @@ function lowerIndexSignatures(node) {
|
|
|
441
447
|
return;
|
|
442
448
|
}
|
|
443
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
|
+
}
|
|
444
470
|
if (members.length !== 1 || members[0].kind !== 'IndexSignature')
|
|
445
471
|
return;
|
|
446
472
|
const signature = members[0];
|
|
@@ -465,6 +491,192 @@ function makeRecordReference(node, keyType, valueType) {
|
|
|
465
491
|
node.typeName = typeName;
|
|
466
492
|
node.typeArguments = [keyType, valueType];
|
|
467
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
|
+
}
|
|
468
680
|
// const Point = class { … } → class Point { … } (the variable names the class)
|
|
469
681
|
function lowerClassExpressions(node) {
|
|
470
682
|
for (const child of childNodes(node))
|
|
@@ -577,9 +789,12 @@ const usedHelpers = new Set();
|
|
|
577
789
|
// per-file import lists can exclude them (helpers are emitted once, in the
|
|
578
790
|
// main file, which carries these imports instead).
|
|
579
791
|
const helperProvidedPackages = new Set();
|
|
792
|
+
// Whether the file being parsed is .tsx
|
|
793
|
+
let currentFileIsTsx = false;
|
|
580
794
|
export function transpileToNative(code, options) {
|
|
581
795
|
fileResolver = options?.readFile ?? null;
|
|
582
796
|
parseFunction = options?.parse ?? defaultParseFunction;
|
|
797
|
+
currentFileIsTsx = !!options?.tsx;
|
|
583
798
|
declaredFunctions.clear();
|
|
584
799
|
declaredInterfaces.clear();
|
|
585
800
|
declaredTypeAliases.clear();
|
|
@@ -685,6 +900,9 @@ function visit(node, options = {}) {
|
|
|
685
900
|
.filter((n) => !!n)
|
|
686
901
|
.join(options.inline ? '' : '\n\t');
|
|
687
902
|
}
|
|
903
|
+
else if (isJsxNode(node)) {
|
|
904
|
+
return visitJsx(node);
|
|
905
|
+
}
|
|
688
906
|
else if (isIdentifier(node) && node.text === 'globalThis') {
|
|
689
907
|
useHelper('globalThis');
|
|
690
908
|
return 'TnGlobalThis';
|
|
@@ -708,6 +926,9 @@ function visit(node, options = {}) {
|
|
|
708
926
|
const goAlias = importAliases.get(node.text);
|
|
709
927
|
if (goAlias)
|
|
710
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)}`;
|
|
711
932
|
// Array references: reads go through the shared slice-header pointer
|
|
712
933
|
if (referenceArrays.has(node.text)) {
|
|
713
934
|
return `(*${getSafeName(node.text)})`;
|
|
@@ -840,6 +1061,8 @@ function visit(node, options = {}) {
|
|
|
840
1061
|
if (regexProperty)
|
|
841
1062
|
return regexProperty;
|
|
842
1063
|
// import.meta.url — the file URL of the running program
|
|
1064
|
+
if (isNewTargetNode(node.expression) && node.name.text === 'name')
|
|
1065
|
+
return '__newTarget';
|
|
843
1066
|
if (node.expression.kind === 'MetaProperty' && node.name.text === 'url') {
|
|
844
1067
|
useHelper('moduleUrl');
|
|
845
1068
|
return 'TnModuleURL()';
|
|
@@ -1197,6 +1420,9 @@ function visit(node, options = {}) {
|
|
|
1197
1420
|
const promiseCall = visitPromiseCall(node);
|
|
1198
1421
|
if (promiseCall)
|
|
1199
1422
|
return promiseCall;
|
|
1423
|
+
const functionCall = visitFunctionCallApply(node);
|
|
1424
|
+
if (functionCall)
|
|
1425
|
+
return functionCall;
|
|
1200
1426
|
const objectCall = visitObjectCall(node);
|
|
1201
1427
|
if (objectCall)
|
|
1202
1428
|
return objectCall;
|
|
@@ -1389,6 +1615,11 @@ function visit(node, options = {}) {
|
|
|
1389
1615
|
return op === '==' ? eq : `!(${eq})`;
|
|
1390
1616
|
}
|
|
1391
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
|
+
}
|
|
1392
1623
|
// obj[key] = v on any
|
|
1393
1624
|
if (op === '=' && isElementAccessExpression(node.left) && isDynamicValue(node.left.expression)) {
|
|
1394
1625
|
useHelper('dynamic');
|
|
@@ -1654,7 +1885,7 @@ function visit(node, options = {}) {
|
|
|
1654
1885
|
if (options.isOutside) {
|
|
1655
1886
|
// async functions run their body in a goroutine sending over a channel
|
|
1656
1887
|
const isAsync = (node.modifiers ?? []).some((m) => m.kind === 'AsyncKeyword');
|
|
1657
|
-
if (isAsync && node.name) {
|
|
1888
|
+
if (isAsync && node.name && !node.asteriskToken) {
|
|
1658
1889
|
return emitChannelFunction(node, true);
|
|
1659
1890
|
}
|
|
1660
1891
|
if (node.asteriskToken && node.name) {
|
|
@@ -1698,6 +1929,10 @@ function visit(node, options = {}) {
|
|
|
1698
1929
|
else if (node.kind === 'ThisKeyword') {
|
|
1699
1930
|
if (thisOverrideName && getEnclosingFunction(node) === thisOverrideFn)
|
|
1700
1931
|
return thisOverrideName;
|
|
1932
|
+
if (isThisOfPlainFunction(node)) {
|
|
1933
|
+
useHelper('decorators');
|
|
1934
|
+
return 'TnThis';
|
|
1935
|
+
}
|
|
1701
1936
|
return 'self';
|
|
1702
1937
|
}
|
|
1703
1938
|
else if (node.kind === 'PrivateIdentifier') {
|
|
@@ -1709,6 +1944,8 @@ function visit(node, options = {}) {
|
|
|
1709
1944
|
enumNames.add(enumName);
|
|
1710
1945
|
enumBaseTypes.set(enumName, getEnumBaseType(node));
|
|
1711
1946
|
if (options.addFunctionOutside) {
|
|
1947
|
+
if (enumHasComputedMember(node))
|
|
1948
|
+
return visitEnumDeclaration(node, true);
|
|
1712
1949
|
outsideNodes.push(node);
|
|
1713
1950
|
return '';
|
|
1714
1951
|
}
|
|
@@ -1796,7 +2033,7 @@ function visit(node, options = {}) {
|
|
|
1796
2033
|
return `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}`;
|
|
1797
2034
|
}
|
|
1798
2035
|
const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
|
|
1799
|
-
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) : ''}`;
|
|
1800
2037
|
}
|
|
1801
2038
|
else if (isClassDeclaration(node)) {
|
|
1802
2039
|
if (options.addFunctionOutside) {
|
|
@@ -1880,7 +2117,7 @@ function visit(node, options = {}) {
|
|
|
1880
2117
|
}
|
|
1881
2118
|
classPropertyTypes.set(className, properties);
|
|
1882
2119
|
classMethodReturnTypes.set(className, methods);
|
|
1883
|
-
return
|
|
2120
|
+
return visitClassDecorators(node);
|
|
1884
2121
|
}
|
|
1885
2122
|
const name = visit(node.name);
|
|
1886
2123
|
const typeParams = getTypeParameters(node.typeParameters);
|
|
@@ -1936,7 +2173,7 @@ function visit(node, options = {}) {
|
|
|
1936
2173
|
const args = (s.expression.arguments ?? []).map((a) => visit(a));
|
|
1937
2174
|
if (extendsError)
|
|
1938
2175
|
return `self.message = ${args[0] ?? '""'}\n\t\t`;
|
|
1939
|
-
return `self.${goFieldName(parentClass)} =
|
|
2176
|
+
return `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, args)}\n\t\t`;
|
|
1940
2177
|
}
|
|
1941
2178
|
return visit(s);
|
|
1942
2179
|
})
|
|
@@ -1945,17 +2182,19 @@ function visit(node, options = {}) {
|
|
|
1945
2182
|
.map((p) => `self.${goFieldName(p.name.text)} = ${visit(p.name)}\n\t\t`)
|
|
1946
2183
|
.join('');
|
|
1947
2184
|
const parentInit = parentClass && !extendsError && !superCalledInCtor
|
|
1948
|
-
? `self.${goFieldName(parentClass)} =
|
|
2185
|
+
? `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, [])}\n\t\t`
|
|
1949
2186
|
: '';
|
|
1950
|
-
|
|
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);
|
|
1951
2189
|
}
|
|
1952
2190
|
else if (extendsError) {
|
|
1953
2191
|
// class X extends Error {} — the constructor takes the message
|
|
1954
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`;
|
|
1955
2193
|
}
|
|
1956
2194
|
else {
|
|
1957
|
-
const parentInit = parentClass && !extendsError ? `self.${goFieldName(parentClass)} =
|
|
1958
|
-
|
|
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);
|
|
1959
2198
|
}
|
|
1960
2199
|
// Inherited concrete methods are copied onto the subclass: Go embedding
|
|
1961
2200
|
// promotes the parent's receiver, which would break `this` dispatch
|
|
@@ -2007,6 +2246,10 @@ function visit(node, options = {}) {
|
|
|
2007
2246
|
if (!methodName || ownMethodNames.has(methodName))
|
|
2008
2247
|
continue;
|
|
2009
2248
|
ownMethodNames.add(methodName);
|
|
2249
|
+
if (getDecorators(member).length > 0) {
|
|
2250
|
+
copiedMethods.push(emitDecoratedMethod(name, typeParamNames, member, visit(ancestor.name)));
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
2010
2253
|
const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
2011
2254
|
const returnType = member.type ? ` ${getType(member.type)}` : '';
|
|
2012
2255
|
copiedMethods.push(`func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n`);
|
|
@@ -2053,7 +2296,10 @@ function visit(node, options = {}) {
|
|
|
2053
2296
|
continue; // abstract method — implemented by subclasses
|
|
2054
2297
|
if (isAbstractClass && !isStatic)
|
|
2055
2298
|
continue; // only reachable via subclasses
|
|
2056
|
-
if (
|
|
2299
|
+
if (getDecorators(member).length > 0) {
|
|
2300
|
+
result += emitDecoratedMethod(name, typeParamNames, member, name);
|
|
2301
|
+
}
|
|
2302
|
+
else if (isStatic) {
|
|
2057
2303
|
// Static methods become package-level functions named ClassName_methodName
|
|
2058
2304
|
result += `func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
|
|
2059
2305
|
}
|
|
@@ -2140,6 +2386,10 @@ function visit(node, options = {}) {
|
|
|
2140
2386
|
}
|
|
2141
2387
|
const typeArgs = getTypeArguments((node.typeArguments ?? []));
|
|
2142
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
|
+
}
|
|
2143
2393
|
return `New${className}${typeArgs}(${args.join(', ')})`;
|
|
2144
2394
|
}
|
|
2145
2395
|
else if (isObjectLiteralExpression(node)) {
|
|
@@ -2181,6 +2431,8 @@ function visit(node, options = {}) {
|
|
|
2181
2431
|
? getTypeArguments(contextualType.typeArguments)
|
|
2182
2432
|
: '';
|
|
2183
2433
|
const structName = typeName.replace(/^\*/, '') + (typeName.endsWith(']') ? '' : typeArgs);
|
|
2434
|
+
if (isMethodInterfaceName(typeName.replace(/^\*/, '')))
|
|
2435
|
+
return visitInterfaceLiteral(node, typeName.replace(/^\*/, ''), typeArgs);
|
|
2184
2436
|
// { ...base, x: 1 } → a copy of *base with fields set
|
|
2185
2437
|
if ((node.properties ?? []).some((p) => p.kind === 'SpreadAssignment')) {
|
|
2186
2438
|
const steps = (node.properties ?? []).map((p) => {
|
|
@@ -2245,6 +2497,8 @@ function visit(node, options = {}) {
|
|
|
2245
2497
|
// Visits a value going into a slot of Go type `goType`; non-nil values for
|
|
2246
2498
|
// nullable primitives (*string/*float64/*bool) are boxed into pointers
|
|
2247
2499
|
function toGoValueOfType(expr, goType) {
|
|
2500
|
+
if (goType === 'string' && inferExpressionType(expr) === 'TnJsxNode')
|
|
2501
|
+
return `string(${visit(expr)})`;
|
|
2248
2502
|
if (goType &&
|
|
2249
2503
|
((isObjectLiteralExpression(expr) && (expr.properties ?? []).length === 0 && goType.startsWith('*TnMap[')) ||
|
|
2250
2504
|
(isArrayLiteralExpression(expr) && (expr.elements ?? []).length === 0 && goType.startsWith('[]')))) {
|
|
@@ -2458,7 +2712,12 @@ function alwaysExits(statement) {
|
|
|
2458
2712
|
function visitBlockStatements(statements) {
|
|
2459
2713
|
const added = [];
|
|
2460
2714
|
const parts = [];
|
|
2461
|
-
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
|
+
}
|
|
2462
2721
|
parts.push(visit(statement));
|
|
2463
2722
|
if (isIfStatement(statement) && !statement.elseStatement && alwaysExits(statement.thenStatement)) {
|
|
2464
2723
|
for (const name of getNarrowedNames(statement.expression, false)) {
|
|
@@ -2473,6 +2732,39 @@ function visitBlockStatements(statements) {
|
|
|
2473
2732
|
narrowedVariables.delete(name);
|
|
2474
2733
|
return parts.join('\t');
|
|
2475
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
|
+
}
|
|
2476
2768
|
function visitDynamicArithmetic(node, op) {
|
|
2477
2769
|
const leftDynamic = isDynamicValue(node.left);
|
|
2478
2770
|
const rightDynamic = isDynamicValue(node.right);
|
|
@@ -2677,6 +2969,66 @@ function visitSpreadCallArguments(node) {
|
|
|
2677
2969
|
return result;
|
|
2678
2970
|
}
|
|
2679
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
|
+
}
|
|
2680
3032
|
function fieldInitializers(classNode) {
|
|
2681
3033
|
return (classNode.members ?? [])
|
|
2682
3034
|
.filter((m) => isPropertyDeclaration(m) &&
|
|
@@ -3598,6 +3950,8 @@ function inferExpressionType(expr) {
|
|
|
3598
3950
|
return '*tnbig.Int';
|
|
3599
3951
|
if (expr.kind === 'TypeOfExpression')
|
|
3600
3952
|
return 'string';
|
|
3953
|
+
if (isJsxNode(expr))
|
|
3954
|
+
return 'TnJsxNode';
|
|
3601
3955
|
if (expr.kind === 'TaggedTemplateExpression' && isStringRawTag(expr.tag))
|
|
3602
3956
|
return 'string';
|
|
3603
3957
|
if (isRegularExpressionLiteral(expr)) {
|
|
@@ -3673,6 +4027,8 @@ function inferExpressionType(expr) {
|
|
|
3673
4027
|
return '[]string';
|
|
3674
4028
|
}
|
|
3675
4029
|
// import.meta.url is a string
|
|
4030
|
+
if (isPropertyAccessExpression(expr) && isNewTargetNode(expr.expression) && expr.name.text === 'name')
|
|
4031
|
+
return 'string';
|
|
3676
4032
|
if (isPropertyAccessExpression(expr) && expr.expression.kind === 'MetaProperty' && expr.name.text === 'url') {
|
|
3677
4033
|
return 'string';
|
|
3678
4034
|
}
|
|
@@ -3751,6 +4107,9 @@ function inferExpressionType(expr) {
|
|
|
3751
4107
|
const regexPropertyType = inferRegexPropertyType(expr);
|
|
3752
4108
|
if (regexPropertyType)
|
|
3753
4109
|
return regexPropertyType;
|
|
4110
|
+
if (isPropertyAccessExpression(expr) && inferExpressionType(expr.expression) === '*TnDecoratorContext') {
|
|
4111
|
+
return ['static', 'private'].includes(expr.name.text) ? 'bool' : 'string';
|
|
4112
|
+
}
|
|
3754
4113
|
if (isPropertyAccessExpression(expr) && expr.name.text === 'length' && hasDynamicProperties(expr.expression)) {
|
|
3755
4114
|
return 'float64';
|
|
3756
4115
|
}
|
|
@@ -3813,6 +4172,9 @@ function inferExpressionType(expr) {
|
|
|
3813
4172
|
// Numeric enums are float64 constants; string enums keep their type
|
|
3814
4173
|
if (enumBaseTypes.get(expr.expression.text) === 'float64')
|
|
3815
4174
|
return 'float64';
|
|
4175
|
+
if (enumBaseTypes.get(expr.expression.text) === 'interface{}') {
|
|
4176
|
+
return enumMemberGoTypes.get(`${expr.expression.text}.${getEnumMemberName(expr.name)}`) ?? 'interface{}';
|
|
4177
|
+
}
|
|
3816
4178
|
const enumType = getSafeName(expr.expression.text);
|
|
3817
4179
|
return enumType;
|
|
3818
4180
|
}
|
|
@@ -4206,6 +4568,7 @@ const BUILTIN_FUNCTION_TYPES = {
|
|
|
4206
4568
|
const STRING_METHOD_RETURN_TYPES = {
|
|
4207
4569
|
trim: 'string',
|
|
4208
4570
|
trimStart: 'string',
|
|
4571
|
+
normalize: 'string',
|
|
4209
4572
|
trimEnd: 'string',
|
|
4210
4573
|
toUpperCase: 'string',
|
|
4211
4574
|
toLowerCase: 'string',
|
|
@@ -4360,6 +4723,46 @@ function makeNullableType(typeName) {
|
|
|
4360
4723
|
// property-only interfaces / object type aliases
|
|
4361
4724
|
// Object types that compile to Go structs: property-only interfaces and
|
|
4362
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
|
+
}
|
|
4363
4766
|
function isObjectTypeName(name) {
|
|
4364
4767
|
const alias = declaredTypeAliases.get(name) ?? typeAliases.get(name);
|
|
4365
4768
|
if (alias)
|
|
@@ -4904,16 +5307,23 @@ function getEnumMemberName(name) {
|
|
|
4904
5307
|
}
|
|
4905
5308
|
return 'Member';
|
|
4906
5309
|
}
|
|
5310
|
+
// string enums, numeric enums, or interface{} for heterogeneous ones (each member keeps its own type)
|
|
4907
5311
|
function getEnumBaseType(node) {
|
|
5312
|
+
let hasString = false;
|
|
5313
|
+
let hasNumber = false;
|
|
4908
5314
|
for (const member of (node.members ?? [])) {
|
|
4909
5315
|
const initializer = member.initializer;
|
|
4910
|
-
if (
|
|
4911
|
-
|
|
4912
|
-
if (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer)) {
|
|
4913
|
-
return 'string';
|
|
5316
|
+
if (initializer && (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer))) {
|
|
5317
|
+
hasString = true;
|
|
4914
5318
|
}
|
|
5319
|
+
else {
|
|
5320
|
+
hasNumber = true;
|
|
5321
|
+
}
|
|
5322
|
+
enumMemberGoTypes.set(`${node.name.text}.${getEnumMemberName(member.name)}`, initializer && (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer)) ? 'string' : 'float64');
|
|
4915
5323
|
}
|
|
4916
|
-
|
|
5324
|
+
if (hasString && hasNumber)
|
|
5325
|
+
return 'interface{}';
|
|
5326
|
+
return hasString ? 'string' : 'float64';
|
|
4917
5327
|
}
|
|
4918
5328
|
function readNumericEnumInitializer(initializer) {
|
|
4919
5329
|
if (isNumericLiteral(initializer)) {
|
|
@@ -4926,18 +5336,28 @@ function readNumericEnumInitializer(initializer) {
|
|
|
4926
5336
|
}
|
|
4927
5337
|
return undefined;
|
|
4928
5338
|
}
|
|
4929
|
-
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) {
|
|
4930
5346
|
const enumName = getSafeName(node.name.text);
|
|
4931
5347
|
const baseType = enumBaseTypes.get(node.name.text) ?? getEnumBaseType(node);
|
|
4932
5348
|
let nextNumericValue = 0;
|
|
4933
5349
|
let canAutoIncrement = true;
|
|
5350
|
+
let hasComputedMember = false;
|
|
4934
5351
|
const members = [];
|
|
4935
5352
|
for (const member of (node.members ?? [])) {
|
|
4936
5353
|
const memberName = getEnumMemberName(member.name);
|
|
4937
5354
|
const symbolName = `${enumName}_${memberName}`;
|
|
4938
5355
|
let valueExpr;
|
|
4939
5356
|
if (member.initializer) {
|
|
4940
|
-
if (baseType === '
|
|
5357
|
+
if (baseType === 'interface{}' && (isStringLiteral(member.initializer) || isNoSubstitutionTemplateLiteral(member.initializer))) {
|
|
5358
|
+
valueExpr = visit(member.initializer);
|
|
5359
|
+
}
|
|
5360
|
+
else if (baseType !== 'string') {
|
|
4941
5361
|
const numericValue = readNumericEnumInitializer(member.initializer);
|
|
4942
5362
|
if (numericValue !== undefined) {
|
|
4943
5363
|
valueExpr = `${numericValue}`;
|
|
@@ -4947,13 +5367,14 @@ function visitEnumDeclaration(node) {
|
|
|
4947
5367
|
else {
|
|
4948
5368
|
valueExpr = `float64(${visit(member.initializer)})`;
|
|
4949
5369
|
canAutoIncrement = false;
|
|
5370
|
+
hasComputedMember = true;
|
|
4950
5371
|
}
|
|
4951
5372
|
}
|
|
4952
5373
|
else {
|
|
4953
5374
|
valueExpr = visit(member.initializer);
|
|
4954
5375
|
}
|
|
4955
5376
|
}
|
|
4956
|
-
else if (baseType
|
|
5377
|
+
else if (baseType !== 'string') {
|
|
4957
5378
|
const currentValue = canAutoIncrement ? nextNumericValue : 0;
|
|
4958
5379
|
valueExpr = `${currentValue}`;
|
|
4959
5380
|
nextNumericValue = currentValue + 1;
|
|
@@ -4963,10 +5384,37 @@ function visitEnumDeclaration(node) {
|
|
|
4963
5384
|
}
|
|
4964
5385
|
members.push(`\t${symbolName} ${enumName} = ${enumName}(${valueExpr})`);
|
|
4965
5386
|
}
|
|
4966
|
-
//
|
|
4967
|
-
|
|
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
|
|
4968
5416
|
if (baseType === 'float64') {
|
|
4969
|
-
return
|
|
5417
|
+
return `${hasComputedMember ? 'var' : 'const'} (\n${members
|
|
4970
5418
|
.map((m) => {
|
|
4971
5419
|
const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
|
|
4972
5420
|
const value = m.slice(m.lastIndexOf('=') + 1).trim();
|
|
@@ -4991,6 +5439,17 @@ function getType(typeNode, getArrayType = false) {
|
|
|
4991
5439
|
}
|
|
4992
5440
|
if (typeNode.kind === 'MappedType')
|
|
4993
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
|
+
}
|
|
4994
5453
|
// readonly T[] → T[]; keyof T → string
|
|
4995
5454
|
if (typeNode.kind === 'TypeOperator') {
|
|
4996
5455
|
return typeNode.operator === 'KeyOfKeyword' ? 'string' : getType(typeNode.type, getArrayType);
|
|
@@ -5030,8 +5489,12 @@ function getType(typeNode, getArrayType = false) {
|
|
|
5030
5489
|
return 'interface{}';
|
|
5031
5490
|
}
|
|
5032
5491
|
if (isFunctionTypeNode(typeNode)) {
|
|
5492
|
+
// (...args: T[]) => R is variadic: func(...T) R
|
|
5033
5493
|
const params = (typeNode.parameters ?? [])
|
|
5034
|
-
.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
|
+
})
|
|
5035
5498
|
.join(', ');
|
|
5036
5499
|
const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
|
|
5037
5500
|
return `func(${params})${ret}`;
|
|
@@ -5051,6 +5514,8 @@ function getType(typeNode, getArrayType = false) {
|
|
|
5051
5514
|
// Numeric enums lowered to float64 constants have no named type
|
|
5052
5515
|
if (enumBaseTypes.get(name) === 'float64')
|
|
5053
5516
|
return 'float64';
|
|
5517
|
+
if (enumBaseTypes.get(name) === 'interface{}')
|
|
5518
|
+
return 'interface{}';
|
|
5054
5519
|
return getSafeName(name);
|
|
5055
5520
|
}
|
|
5056
5521
|
const aliasType = getAliasType(name);
|
|
@@ -5581,6 +6046,15 @@ const stringMethodHandlers = {
|
|
|
5581
6046
|
return `${obj}[${sliceIndex(obj, args[0])}:]`;
|
|
5582
6047
|
},
|
|
5583
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
|
+
},
|
|
5584
6058
|
padStart: (obj, args) => {
|
|
5585
6059
|
importedPackages.add('strings');
|
|
5586
6060
|
const pad = args[1] ?? '" "';
|
|
@@ -6186,6 +6660,216 @@ function visitRegexProperty(node) {
|
|
|
6186
6660
|
importedPackages.add('strings');
|
|
6187
6661
|
return `strings.Contains(TnRegexpFlags(${receiver}), "${REGEX_FLAG_PROPERTIES[name]}")`;
|
|
6188
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
|
+
}
|
|
6189
6873
|
function isStringRawTag(tag) {
|
|
6190
6874
|
return isPropertyAccessExpression(tag) && isIdentifier(tag.expression) && tag.expression.text === 'String' && tag.name.text === 'raw';
|
|
6191
6875
|
}
|
|
@@ -6361,6 +7045,14 @@ function visitCallArguments(node) {
|
|
|
6361
7045
|
return result;
|
|
6362
7046
|
}
|
|
6363
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
|
+
}
|
|
6364
7056
|
const contextualFn = getContextualFunctionType(fn);
|
|
6365
7057
|
const declaredCount = (fn.parameters ?? []).length;
|
|
6366
7058
|
const contextualParams = contextualFn?.parameters ?? [];
|
|
@@ -6372,6 +7064,16 @@ function withContextualParameters(fn, info) {
|
|
|
6372
7064
|
const signature = [info.signature, ...extra].filter((part) => part).join(', ');
|
|
6373
7065
|
return { ...info, signature };
|
|
6374
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
|
+
}
|
|
6375
7077
|
// Destructured parameters ({ a, b }: T / [x, y]: T[]) get a temporary name
|
|
6376
7078
|
function getParameterName(param, parameters) {
|
|
6377
7079
|
if (isIdentifier(param.name))
|
|
@@ -6612,8 +7314,8 @@ function visitNewSet(node) {
|
|
|
6612
7314
|
elementType = getArrayLiteralElementType(node.arguments[0]);
|
|
6613
7315
|
}
|
|
6614
7316
|
const args = node.arguments ?? [];
|
|
6615
|
-
// new Set(otherSet/values()) — copy at runtime
|
|
6616
|
-
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)))) {
|
|
6617
7319
|
useHelper('orderedMap');
|
|
6618
7320
|
const src = getTempName('setsrc');
|
|
6619
7321
|
const item = getTempName('item');
|
|
@@ -6648,9 +7350,11 @@ function normalizeCjsToEsm(code) {
|
|
|
6648
7350
|
code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
|
|
6649
7351
|
return code;
|
|
6650
7352
|
}
|
|
6651
|
-
function includeLocalImport(code, dir, goFileName) {
|
|
7353
|
+
function includeLocalImport(code, dir, goFileName, tsx = false) {
|
|
6652
7354
|
const prevDir = currentFileDir;
|
|
7355
|
+
const prevTsx = currentFileIsTsx;
|
|
6653
7356
|
currentFileDir = dir;
|
|
7357
|
+
currentFileIsTsx = tsx;
|
|
6654
7358
|
// Normalize CommonJS modules to ES module syntax before parsing
|
|
6655
7359
|
if (!code.includes('export ') && (code.includes('module.exports') || code.includes('exports.'))) {
|
|
6656
7360
|
code = normalizeCjsToEsm(code);
|
|
@@ -6662,6 +7366,7 @@ function includeLocalImport(code, dir, goFileName) {
|
|
|
6662
7366
|
visit(stmt, { addFunctionOutside: true });
|
|
6663
7367
|
}
|
|
6664
7368
|
currentFileDir = prevDir;
|
|
7369
|
+
currentFileIsTsx = prevTsx;
|
|
6665
7370
|
return;
|
|
6666
7371
|
}
|
|
6667
7372
|
// Separate-file mode (local TS imports): transpile to its own Go file.
|
|
@@ -6683,7 +7388,13 @@ function includeLocalImport(code, dir, goFileName) {
|
|
|
6683
7388
|
}
|
|
6684
7389
|
emittingModuleFile = prevEmittingModuleFile;
|
|
6685
7390
|
const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
6686
|
-
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');
|
|
6687
7398
|
// Computed after all visits: function bodies (outside nodes) register
|
|
6688
7399
|
// packages too, and imports must be captured after those visits.
|
|
6689
7400
|
// Helper-provided packages are only imported here if the file's own code
|
|
@@ -6714,6 +7425,7 @@ function includeLocalImport(code, dir, goFileName) {
|
|
|
6714
7425
|
importedPackages.clear();
|
|
6715
7426
|
for (const p of savedPackages)
|
|
6716
7427
|
importedPackages.add(p);
|
|
7428
|
+
currentFileIsTsx = prevTsx;
|
|
6717
7429
|
for (const p of helperProvidedPackages)
|
|
6718
7430
|
importedPackages.add(p);
|
|
6719
7431
|
currentFileDir = prevDir;
|
|
@@ -6778,6 +7490,8 @@ const helperRequires = {
|
|
|
6778
7490
|
objectAssign: ['orderedMap'],
|
|
6779
7491
|
promiseAll: ['orderedMap', 'asyncWait', 'promise'],
|
|
6780
7492
|
promise: ['error'],
|
|
7493
|
+
decorators: [],
|
|
7494
|
+
jsx: [],
|
|
6781
7495
|
objectIntegrity: [],
|
|
6782
7496
|
structuredClone: [],
|
|
6783
7497
|
globalThis: ['dynamic'],
|
|
@@ -6979,6 +7693,8 @@ const helperPackages = {
|
|
|
6979
7693
|
uri: ['strings', 'fmt', 'net/url'],
|
|
6980
7694
|
promiseAll: [],
|
|
6981
7695
|
promise: ['sync'],
|
|
7696
|
+
decorators: [],
|
|
7697
|
+
jsx: ['strings', 'html', 'reflect', 'strconv', 'fmt'],
|
|
6982
7698
|
objectIntegrity: ['sync', 'reflect'],
|
|
6983
7699
|
structuredClone: ['reflect', 'unsafe'],
|
|
6984
7700
|
globalThis: [],
|
|
@@ -7420,14 +8136,30 @@ func TnIsNil(v interface{}) bool {
|
|
|
7420
8136
|
return false
|
|
7421
8137
|
}`,
|
|
7422
8138
|
typeOf: `func TnTypeOf(v interface{}) string {
|
|
7423
|
-
|
|
8139
|
+
// an unset nullable primitive (*T nil) is undefined
|
|
8140
|
+
switch t := v.(type) {
|
|
7424
8141
|
case nil:
|
|
7425
8142
|
return "undefined"
|
|
7426
|
-
case string
|
|
8143
|
+
case string:
|
|
7427
8144
|
return "string"
|
|
7428
|
-
case
|
|
8145
|
+
case *string:
|
|
8146
|
+
if t == nil {
|
|
8147
|
+
return "undefined"
|
|
8148
|
+
}
|
|
8149
|
+
return "string"
|
|
8150
|
+
case float64:
|
|
8151
|
+
return "number"
|
|
8152
|
+
case *float64:
|
|
8153
|
+
if t == nil {
|
|
8154
|
+
return "undefined"
|
|
8155
|
+
}
|
|
7429
8156
|
return "number"
|
|
7430
|
-
case bool
|
|
8157
|
+
case bool:
|
|
8158
|
+
return "boolean"
|
|
8159
|
+
case *bool:
|
|
8160
|
+
if t == nil {
|
|
8161
|
+
return "undefined"
|
|
8162
|
+
}
|
|
7431
8163
|
return "boolean"
|
|
7432
8164
|
}
|
|
7433
8165
|
if reflect.ValueOf(v).Kind() == reflect.Func {
|
|
@@ -8994,6 +9726,128 @@ func tnCloneValue(v reflect.Value) reflect.Value {
|
|
|
8994
9726
|
}`,
|
|
8995
9727
|
globalThis: `// (globalThis as any).x: properties set on the global object at runtime
|
|
8996
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
|
+
}`,
|
|
8997
9851
|
promise: `// Promises are channels; a rejected promise is a closed channel with a reason
|
|
8998
9852
|
var tnRejections sync.Map
|
|
8999
9853
|
|
|
@@ -9347,11 +10201,13 @@ function visitImportDeclaration(node) {
|
|
|
9347
10201
|
const resolved = fileResolver(moduleSpec, currentFileDir);
|
|
9348
10202
|
if (resolved) {
|
|
9349
10203
|
const goFileName = specifierToGoFileName(moduleSpec);
|
|
9350
|
-
includeLocalImport(resolved.content, resolved.dir, goFileName);
|
|
10204
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName, !!resolved.tsx);
|
|
9351
10205
|
}
|
|
9352
10206
|
}
|
|
9353
10207
|
// Renamed bindings refer to the original declaration (one Go package)
|
|
9354
10208
|
const bindings = node.importClause?.namedBindings;
|
|
10209
|
+
if (bindings && isNamespaceImport(bindings))
|
|
10210
|
+
defaultImportNamespaces.add(bindings.name.text);
|
|
9355
10211
|
if (bindings && isNamedImports(bindings)) {
|
|
9356
10212
|
for (const el of bindings.elements ?? []) {
|
|
9357
10213
|
if (!el.propertyName)
|
|
@@ -9407,7 +10263,8 @@ function visitImportDeclaration(node) {
|
|
|
9407
10263
|
// variables (a variable named `big` would shadow the package name)
|
|
9408
10264
|
const goImportAliases = {
|
|
9409
10265
|
'math/big': 'tnbig',
|
|
9410
|
-
'net/url': 'tnurl'
|
|
10266
|
+
'net/url': 'tnurl',
|
|
10267
|
+
'golang.org/x/text/unicode/norm': 'tnnorm'
|
|
9411
10268
|
};
|
|
9412
10269
|
function goImportLine(pkg) {
|
|
9413
10270
|
const alias = goImportAliases[pkg];
|