typenative 0.0.17 → 0.0.19
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/.github/workflows/npm-publish-github-packages.yml +75 -36
- package/CHANGELOG.md +119 -95
- package/LICENSE +21 -21
- package/README.md +158 -152
- package/TODO.md +29 -0
- package/bin/index.js +176 -33
- package/bin/transpiler.js +502 -19
- package/go.d.ts +1 -0
- package/index.d.ts +1 -0
- package/npm.d.ts +1 -0
- package/package.json +68 -59
- package/types/typenative-go.d.ts +15 -0
- package/types/typenative-npm.d.ts +5 -0
- package/types/typenative.d.ts +249 -193
package/bin/transpiler.js
CHANGED
|
@@ -17,7 +17,22 @@ const interfacePropertyTypes = new Map();
|
|
|
17
17
|
const typeAliases = new Map();
|
|
18
18
|
const enumNames = new Set();
|
|
19
19
|
const enumBaseTypes = new Map();
|
|
20
|
-
|
|
20
|
+
// Maps local TS name → Go qualified name (e.g. 'Println' → 'fmt.Println', 'myFmt' → 'fmt')
|
|
21
|
+
const importAliases = new Map();
|
|
22
|
+
// Callback for resolving import specifiers to source code.
|
|
23
|
+
// specifier: the raw import string (relative path or package name)
|
|
24
|
+
// fromDir: directory of the file containing the import (null = main file's dir)
|
|
25
|
+
// Returns the file content and its directory (for resolving that file's own imports)
|
|
26
|
+
let fileResolver = null;
|
|
27
|
+
// Directory of the file currently being processed (null = main entry file)
|
|
28
|
+
let currentFileDir = null;
|
|
29
|
+
// Tracks already-included files by a stable key to prevent duplicates/cycles
|
|
30
|
+
const includedLocalImports = new Set();
|
|
31
|
+
// Collects Go source files generated from local TS imports (filename → content)
|
|
32
|
+
let localImportFiles = new Map();
|
|
33
|
+
export function transpileToNative(code, options) {
|
|
34
|
+
fileResolver = options?.readFile ?? null;
|
|
35
|
+
currentFileDir = null;
|
|
21
36
|
const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
22
37
|
TypeCheker = ts.createProgram(['main.ts'], {}).getTypeChecker();
|
|
23
38
|
importedPackages.clear();
|
|
@@ -34,17 +49,21 @@ export function transpileToNative(code) {
|
|
|
34
49
|
typeAliases.clear();
|
|
35
50
|
enumNames.clear();
|
|
36
51
|
enumBaseTypes.clear();
|
|
52
|
+
importAliases.clear();
|
|
53
|
+
includedLocalImports.clear();
|
|
54
|
+
localImportFiles = new Map();
|
|
37
55
|
const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
|
|
38
56
|
const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
${[...importedPackages].map((pkg) => `import "${pkg}"`).join('\n')}
|
|
42
|
-
|
|
43
|
-
func main() {
|
|
44
|
-
${transpiledCode.trim()}
|
|
45
|
-
}
|
|
46
|
-
|
|
57
|
+
const main = `package main
|
|
58
|
+
|
|
59
|
+
${[...importedPackages].map((pkg) => `import "${pkg}"`).join('\n')}
|
|
60
|
+
|
|
61
|
+
func main() {
|
|
62
|
+
${transpiledCode.trim()}
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
${transpiledCodeOutside.trim()}`;
|
|
66
|
+
return { main, files: localImportFiles };
|
|
48
67
|
}
|
|
49
68
|
export function visit(node, options = {}) {
|
|
50
69
|
let code = '';
|
|
@@ -57,6 +76,9 @@ export function visit(node, options = {}) {
|
|
|
57
76
|
else if (ts.isIdentifier(node)) {
|
|
58
77
|
if (node.text === 'undefined')
|
|
59
78
|
return 'nil';
|
|
79
|
+
const goAlias = importAliases.get(node.text);
|
|
80
|
+
if (goAlias)
|
|
81
|
+
return goAlias;
|
|
60
82
|
return getSafeName(node.text);
|
|
61
83
|
}
|
|
62
84
|
else if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
@@ -237,9 +259,23 @@ export function visit(node, options = {}) {
|
|
|
237
259
|
})}; ${visit(node.incrementor, { inline: true })}${visit(node.statement)}`;
|
|
238
260
|
}
|
|
239
261
|
else if (ts.isForOfStatement(node)) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
262
|
+
const iterExpr = visit(node.expression, { inline: true });
|
|
263
|
+
const iterType = inferExpressionType(node.expression);
|
|
264
|
+
if (iterType && iterType.startsWith('map[')) {
|
|
265
|
+
const valueType = extractMapValueType(iterType);
|
|
266
|
+
const isSet = valueType === 'struct{}';
|
|
267
|
+
const varInfo = getForOfVarNames(node.initializer);
|
|
268
|
+
if (isSet) {
|
|
269
|
+
return `for ${varInfo[0]} := range ${iterExpr}${visit(node.statement)}`;
|
|
270
|
+
}
|
|
271
|
+
else if (varInfo.length >= 2) {
|
|
272
|
+
return `for ${varInfo[0]}, ${varInfo[1]} := range ${iterExpr}${visit(node.statement)}`;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
return `for ${varInfo[0]} := range ${iterExpr}${visit(node.statement)}`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return `for _,${visit(node.initializer, { inline: true })}= range ${iterExpr}${visit(node.statement)}`;
|
|
243
279
|
}
|
|
244
280
|
else if (ts.isWhileStatement(node)) {
|
|
245
281
|
return `for ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
|
|
@@ -281,6 +317,20 @@ export function visit(node, options = {}) {
|
|
|
281
317
|
else if (ts.isBreakStatement(node)) {
|
|
282
318
|
return 'break';
|
|
283
319
|
}
|
|
320
|
+
else if (ts.isThrowStatement(node)) {
|
|
321
|
+
const expr = node.expression;
|
|
322
|
+
if (ts.isNewExpression(expr) &&
|
|
323
|
+
ts.isIdentifier(expr.expression) &&
|
|
324
|
+
expr.expression.text === 'Error') {
|
|
325
|
+
const args = expr.arguments ?? [];
|
|
326
|
+
const msg = args.length > 0 ? visit(args[0]) : '""';
|
|
327
|
+
return `panic(${msg})` + (options.inline ? '' : ';\n\t');
|
|
328
|
+
}
|
|
329
|
+
return `panic(${visit(expr)})` + (options.inline ? '' : ';\n\t');
|
|
330
|
+
}
|
|
331
|
+
else if (ts.isTryStatement(node)) {
|
|
332
|
+
return visitTryStatement(node, options);
|
|
333
|
+
}
|
|
284
334
|
else if (ts.isReturnStatement(node)) {
|
|
285
335
|
// Handle return new Promise(...)
|
|
286
336
|
if (node.expression &&
|
|
@@ -296,26 +346,41 @@ export function visit(node, options = {}) {
|
|
|
296
346
|
outsideNodes.push(node);
|
|
297
347
|
return '';
|
|
298
348
|
}
|
|
299
|
-
const name = visit(node.name, { inline: true });
|
|
300
|
-
const safeName = getSafeName(name);
|
|
301
349
|
const typeParams = getTypeParameters(node.typeParameters);
|
|
302
350
|
const parameterInfo = getFunctionParametersInfo(node.parameters);
|
|
303
|
-
|
|
351
|
+
if (node.body && ts.isBlock(node.body)) {
|
|
352
|
+
prescanVariableDeclarations(node.body);
|
|
353
|
+
}
|
|
354
|
+
const inferredRetType = inferFunctionBodyReturnType(node);
|
|
355
|
+
const returnType = inferredRetType ? ` ${inferredRetType}` : '';
|
|
304
356
|
if (options.isOutside) {
|
|
357
|
+
const name = node.name ? visit(node.name, { inline: true }) : '';
|
|
358
|
+
const safeName = getSafeName(name);
|
|
305
359
|
return `func ${safeName}${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
|
|
306
360
|
prefixBlockContent: parameterInfo.prefixBlockContent
|
|
307
361
|
})}`;
|
|
308
362
|
}
|
|
363
|
+
if (!node.name) {
|
|
364
|
+
return `func${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
|
|
365
|
+
prefixBlockContent: parameterInfo.prefixBlockContent
|
|
366
|
+
})}`;
|
|
367
|
+
}
|
|
368
|
+
const name = visit(node.name, { inline: true });
|
|
369
|
+
const safeName = getSafeName(name);
|
|
309
370
|
return `${safeName} := func${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
|
|
310
371
|
prefixBlockContent: parameterInfo.prefixBlockContent
|
|
311
372
|
})}`;
|
|
312
373
|
}
|
|
313
374
|
else if (ts.isArrowFunction(node)) {
|
|
314
375
|
const parameterInfo = getFunctionParametersInfo(node.parameters);
|
|
315
|
-
const
|
|
376
|
+
const inferredRetType = inferFunctionBodyReturnType(node);
|
|
377
|
+
const returnType = inferredRetType ? ` ${inferredRetType}` : '';
|
|
316
378
|
if (parameterInfo.prefixBlockContent && !ts.isBlock(node.body)) {
|
|
317
379
|
return `func(${parameterInfo.signature})${returnType} {\n\t\t${parameterInfo.prefixBlockContent}return ${visit(node.body)};\n\t}`;
|
|
318
380
|
}
|
|
381
|
+
if (!ts.isBlock(node.body)) {
|
|
382
|
+
return `func(${parameterInfo.signature})${returnType} { return ${visit(node.body)}; }`;
|
|
383
|
+
}
|
|
319
384
|
return `func(${parameterInfo.signature})${returnType} ${visit(node.body, {
|
|
320
385
|
prefixBlockContent: parameterInfo.prefixBlockContent
|
|
321
386
|
})}`;
|
|
@@ -480,6 +545,12 @@ export function visit(node, options = {}) {
|
|
|
480
545
|
}
|
|
481
546
|
return `regexp.MustCompile("")`;
|
|
482
547
|
}
|
|
548
|
+
if (className === 'Map') {
|
|
549
|
+
return visitNewMap(node);
|
|
550
|
+
}
|
|
551
|
+
if (className === 'Set') {
|
|
552
|
+
return visitNewSet(node);
|
|
553
|
+
}
|
|
483
554
|
const typeArgs = getTypeArguments(node.typeArguments);
|
|
484
555
|
const args = node.arguments ? node.arguments.map((a) => visit(a)) : [];
|
|
485
556
|
return `New${className}${typeArgs}(${args.join(', ')})`;
|
|
@@ -506,6 +577,12 @@ export function visit(node, options = {}) {
|
|
|
506
577
|
else if (ts.isNonNullExpression(node)) {
|
|
507
578
|
return visit(node.expression);
|
|
508
579
|
}
|
|
580
|
+
else if (ts.isImportDeclaration(node)) {
|
|
581
|
+
return visitImportDeclaration(node);
|
|
582
|
+
}
|
|
583
|
+
else if (ts.isExportDeclaration(node) || ts.isExportAssignment(node)) {
|
|
584
|
+
return '';
|
|
585
|
+
}
|
|
509
586
|
const syntaxKind = ts.SyntaxKind[node.kind];
|
|
510
587
|
if (!['FirstStatement', 'EndOfFileToken'].includes(syntaxKind)) {
|
|
511
588
|
console.log(ts.SyntaxKind[node.kind], node.getText());
|
|
@@ -571,7 +648,50 @@ function inferExpectedTypeFromContext(node) {
|
|
|
571
648
|
}
|
|
572
649
|
return undefined;
|
|
573
650
|
}
|
|
651
|
+
function prescanVariableDeclarations(block) {
|
|
652
|
+
for (const stmt of block.statements) {
|
|
653
|
+
if (ts.isVariableStatement(stmt)) {
|
|
654
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
655
|
+
if (ts.isIdentifier(decl.name) && !variableGoTypes.has(decl.name.text)) {
|
|
656
|
+
if (decl.type) {
|
|
657
|
+
variableGoTypes.set(decl.name.text, getType(decl.type));
|
|
658
|
+
}
|
|
659
|
+
else if (decl.initializer) {
|
|
660
|
+
const inferredType = inferExpressionType(decl.initializer);
|
|
661
|
+
if (inferredType)
|
|
662
|
+
variableGoTypes.set(decl.name.text, inferredType);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function inferFunctionBodyReturnType(node) {
|
|
670
|
+
if (node.type)
|
|
671
|
+
return getType(node.type);
|
|
672
|
+
if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
|
|
673
|
+
return inferExpressionType(node.body);
|
|
674
|
+
}
|
|
675
|
+
if (node.body && ts.isBlock(node.body)) {
|
|
676
|
+
for (const stmt of node.body.statements) {
|
|
677
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
678
|
+
const exprType = inferExpressionType(stmt.expression);
|
|
679
|
+
if (exprType)
|
|
680
|
+
return exprType;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
function inferArrowFunctionGoType(node) {
|
|
687
|
+
const params = node.parameters.map((p) => (p.type ? getType(p.type) : 'interface{}')).join(', ');
|
|
688
|
+
const retType = inferFunctionBodyReturnType(node);
|
|
689
|
+
return `func(${params})${retType ? ` ${retType}` : ''}`;
|
|
690
|
+
}
|
|
574
691
|
function inferExpressionType(expr) {
|
|
692
|
+
if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
|
|
693
|
+
return inferArrowFunctionGoType(expr);
|
|
694
|
+
}
|
|
575
695
|
if (ts.isParenthesizedExpression(expr))
|
|
576
696
|
return inferExpressionType(expr.expression);
|
|
577
697
|
if (ts.isNonNullExpression(expr))
|
|
@@ -599,6 +719,15 @@ function inferExpressionType(expr) {
|
|
|
599
719
|
const firstElementType = inferExpressionType(expr.elements[0]) ?? 'interface{}';
|
|
600
720
|
return `[]${firstElementType}`;
|
|
601
721
|
}
|
|
722
|
+
if (ts.isNewExpression(expr) && ts.isIdentifier(expr.expression)) {
|
|
723
|
+
const ctorName = expr.expression.text;
|
|
724
|
+
if (ctorName === 'Map' && expr.typeArguments && expr.typeArguments.length === 2) {
|
|
725
|
+
return `map[${getType(expr.typeArguments[0])}]${getType(expr.typeArguments[1])}`;
|
|
726
|
+
}
|
|
727
|
+
if (ctorName === 'Set' && expr.typeArguments && expr.typeArguments.length === 1) {
|
|
728
|
+
return `map[${getType(expr.typeArguments[0])}]struct{}`;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
602
731
|
if (ts.isPropertyAccessExpression(expr)) {
|
|
603
732
|
if (ts.isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
|
|
604
733
|
const enumType = getSafeName(expr.expression.text);
|
|
@@ -634,6 +763,12 @@ function inferExpressionType(expr) {
|
|
|
634
763
|
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
|
|
635
764
|
const methodName = expr.expression.name.text;
|
|
636
765
|
const ownerType = inferExpressionType(expr.expression.expression);
|
|
766
|
+
if (ownerType && ownerType.startsWith('map[')) {
|
|
767
|
+
if (methodName === 'has')
|
|
768
|
+
return 'bool';
|
|
769
|
+
if (methodName === 'get')
|
|
770
|
+
return extractMapValueType(ownerType);
|
|
771
|
+
}
|
|
637
772
|
if (isArrayLikeGoType(ownerType)) {
|
|
638
773
|
const elementType = getArrayElementTypeFromGoType(ownerType);
|
|
639
774
|
if (methodName === 'map') {
|
|
@@ -866,6 +1001,9 @@ function visitArrayHigherOrderCall(node) {
|
|
|
866
1001
|
? getArrayElementTypeFromGoType(ownerType)
|
|
867
1002
|
: 'interface{}';
|
|
868
1003
|
if (methodName === 'join') {
|
|
1004
|
+
// Only intercept array.join — if owner type is unknown/not array, fall through to callHandlers
|
|
1005
|
+
if (!isArrayLikeGoType(ownerType))
|
|
1006
|
+
return undefined;
|
|
869
1007
|
importedPackages.add('strings');
|
|
870
1008
|
importedPackages.add('fmt');
|
|
871
1009
|
const separator = node.arguments[0] ? visit(node.arguments[0]) : '""';
|
|
@@ -1026,6 +1164,13 @@ function getType(typeNode, getArrayType = false) {
|
|
|
1026
1164
|
// Non-nullable union or multi-type union → interface{}
|
|
1027
1165
|
return 'interface{}';
|
|
1028
1166
|
}
|
|
1167
|
+
if (ts.isFunctionTypeNode(typeNode)) {
|
|
1168
|
+
const params = typeNode.parameters
|
|
1169
|
+
.map((p) => (p.type ? getType(p.type) : 'interface{}'))
|
|
1170
|
+
.join(', ');
|
|
1171
|
+
const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
|
|
1172
|
+
return `func(${params})${ret}`;
|
|
1173
|
+
}
|
|
1029
1174
|
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
1030
1175
|
const name = typeNode.typeName.text;
|
|
1031
1176
|
if (enumNames.has(name)) {
|
|
@@ -1041,6 +1186,15 @@ function getType(typeNode, getArrayType = false) {
|
|
|
1041
1186
|
if (name === 'RegExp') {
|
|
1042
1187
|
return '*regexp.Regexp';
|
|
1043
1188
|
}
|
|
1189
|
+
if (name === 'Map' && typeNode.typeArguments && typeNode.typeArguments.length === 2) {
|
|
1190
|
+
const keyType = getType(typeNode.typeArguments[0]);
|
|
1191
|
+
const valueType = getType(typeNode.typeArguments[1]);
|
|
1192
|
+
return `map[${keyType}]${valueType}`;
|
|
1193
|
+
}
|
|
1194
|
+
if (name === 'Set' && typeNode.typeArguments && typeNode.typeArguments.length === 1) {
|
|
1195
|
+
const elementType = getType(typeNode.typeArguments[0]);
|
|
1196
|
+
return `map[${elementType}]struct{}`;
|
|
1197
|
+
}
|
|
1044
1198
|
const typeArgs = getTypeArguments(typeNode.typeArguments);
|
|
1045
1199
|
if (classNames.has(name)) {
|
|
1046
1200
|
return `*${name}${typeArgs}`;
|
|
@@ -1091,6 +1245,10 @@ function getTypeCategory(typeNode) {
|
|
|
1091
1245
|
}
|
|
1092
1246
|
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
1093
1247
|
const name = typeNode.typeName.text;
|
|
1248
|
+
if (name === 'Map')
|
|
1249
|
+
return 'Map';
|
|
1250
|
+
if (name === 'Set')
|
|
1251
|
+
return 'Set';
|
|
1094
1252
|
if (classNames.has(name))
|
|
1095
1253
|
return 'class';
|
|
1096
1254
|
return name;
|
|
@@ -1131,6 +1289,9 @@ function getAcessString(leftSide, rightSide, objectType) {
|
|
|
1131
1289
|
if (rightSide === 'length' && objectType !== 'class') {
|
|
1132
1290
|
return `float64(len(${leftSide}))`;
|
|
1133
1291
|
}
|
|
1292
|
+
if (rightSide === 'size' && (objectType === 'Map' || objectType === 'Set')) {
|
|
1293
|
+
return `float64(len(${leftSide}))`;
|
|
1294
|
+
}
|
|
1134
1295
|
return `${leftSide}.${rightSide}`;
|
|
1135
1296
|
}
|
|
1136
1297
|
const callHandlers = {
|
|
@@ -1286,6 +1447,25 @@ const arrayMethodHandlers = {
|
|
|
1286
1447
|
return `fmt.Sprintf("%v", ${obj})`;
|
|
1287
1448
|
}
|
|
1288
1449
|
};
|
|
1450
|
+
const mapMethodHandlers = {
|
|
1451
|
+
set: (obj, args) => `${obj}[${args[0]}] = ${args[1]}`,
|
|
1452
|
+
get: (obj, args) => `${obj}[${args[0]}]`,
|
|
1453
|
+
has: (obj, args) => {
|
|
1454
|
+
const tmp = getTempName('ok');
|
|
1455
|
+
return `func() bool { _, ${tmp} := ${obj}[${args[0]}]; return ${tmp} }()`;
|
|
1456
|
+
},
|
|
1457
|
+
delete: (obj, args) => `delete(${obj}, ${args[0]})`,
|
|
1458
|
+
clear: (obj) => `clear(${obj})`
|
|
1459
|
+
};
|
|
1460
|
+
const setMethodHandlers = {
|
|
1461
|
+
add: (obj, args) => `${obj}[${args[0]}] = struct{}{}`,
|
|
1462
|
+
has: (obj, args) => {
|
|
1463
|
+
const tmp = getTempName('ok');
|
|
1464
|
+
return `func() bool { _, ${tmp} := ${obj}[${args[0]}]; return ${tmp} }()`;
|
|
1465
|
+
},
|
|
1466
|
+
delete: (obj, args) => `delete(${obj}, ${args[0]})`,
|
|
1467
|
+
clear: (obj) => `clear(${obj})`
|
|
1468
|
+
};
|
|
1289
1469
|
function getDynamicCallHandler(caller, objectType) {
|
|
1290
1470
|
if (promiseResolveName && caller === promiseResolveName) {
|
|
1291
1471
|
return (_caller, args) => `ch <- ${args[0]}`;
|
|
@@ -1314,6 +1494,12 @@ function getDynamicCallHandler(caller, objectType) {
|
|
|
1314
1494
|
else if (objectType === 'RegExp') {
|
|
1315
1495
|
handler = regexpMethodHandlers[methodName];
|
|
1316
1496
|
}
|
|
1497
|
+
else if (objectType === 'Map') {
|
|
1498
|
+
handler = mapMethodHandlers[methodName];
|
|
1499
|
+
}
|
|
1500
|
+
else if (objectType === 'Set') {
|
|
1501
|
+
handler = setMethodHandlers[methodName];
|
|
1502
|
+
}
|
|
1317
1503
|
else {
|
|
1318
1504
|
// Unknown type: try both maps for backward compatibility
|
|
1319
1505
|
handler =
|
|
@@ -1415,9 +1601,7 @@ function getFunctionParametersInfo(parameters) {
|
|
|
1415
1601
|
prefixBlockContent: ''
|
|
1416
1602
|
};
|
|
1417
1603
|
}
|
|
1418
|
-
const hasRequiredAfterDefault = parameters
|
|
1419
|
-
.slice(firstDefaultIndex)
|
|
1420
|
-
.some((p) => !p.initializer);
|
|
1604
|
+
const hasRequiredAfterDefault = parameters.slice(firstDefaultIndex).some((p) => !p.initializer);
|
|
1421
1605
|
if (hasRequiredAfterDefault) {
|
|
1422
1606
|
return {
|
|
1423
1607
|
signature: parameters.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`).join(', '),
|
|
@@ -1503,3 +1687,302 @@ function visitNewPromise(node) {
|
|
|
1503
1687
|
promiseResolveName = prevResolveName;
|
|
1504
1688
|
return `func() chan ${channelType} {\n\t\tch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch;\n\t}()`;
|
|
1505
1689
|
}
|
|
1690
|
+
function extractMapValueType(mapType) {
|
|
1691
|
+
// mapType is "map[K]V" — find the closing bracket of K accounting for nesting
|
|
1692
|
+
if (!mapType.startsWith('map['))
|
|
1693
|
+
return 'interface{}';
|
|
1694
|
+
let depth = 0;
|
|
1695
|
+
for (let i = 4; i < mapType.length; i++) {
|
|
1696
|
+
if (mapType[i] === '[')
|
|
1697
|
+
depth++;
|
|
1698
|
+
else if (mapType[i] === ']') {
|
|
1699
|
+
if (depth === 0)
|
|
1700
|
+
return mapType.substring(i + 1);
|
|
1701
|
+
depth--;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return 'interface{}';
|
|
1705
|
+
}
|
|
1706
|
+
function visitNewMap(node) {
|
|
1707
|
+
let keyType = 'interface{}';
|
|
1708
|
+
let valueType = 'interface{}';
|
|
1709
|
+
if (node.typeArguments && node.typeArguments.length === 2) {
|
|
1710
|
+
keyType = getType(node.typeArguments[0]);
|
|
1711
|
+
valueType = getType(node.typeArguments[1]);
|
|
1712
|
+
}
|
|
1713
|
+
const mapType = `map[${keyType}]${valueType}`;
|
|
1714
|
+
const args = node.arguments;
|
|
1715
|
+
if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
|
|
1716
|
+
return `make(${mapType})`;
|
|
1717
|
+
}
|
|
1718
|
+
const initArg = args[0];
|
|
1719
|
+
const tmp = getTempName('map');
|
|
1720
|
+
const entries = initArg.elements
|
|
1721
|
+
.filter((el) => ts.isArrayLiteralExpression(el) && el.elements.length >= 2)
|
|
1722
|
+
.map((el) => {
|
|
1723
|
+
const pair = el;
|
|
1724
|
+
return `${tmp}[${visit(pair.elements[0])}] = ${visit(pair.elements[1])}`;
|
|
1725
|
+
})
|
|
1726
|
+
.join('; ');
|
|
1727
|
+
return `func() ${mapType} { ${tmp} := make(${mapType}); ${entries}; return ${tmp} }()`;
|
|
1728
|
+
}
|
|
1729
|
+
function visitNewSet(node) {
|
|
1730
|
+
let elementType = 'interface{}';
|
|
1731
|
+
if (node.typeArguments && node.typeArguments.length === 1) {
|
|
1732
|
+
elementType = getType(node.typeArguments[0]);
|
|
1733
|
+
}
|
|
1734
|
+
const setType = `map[${elementType}]struct{}`;
|
|
1735
|
+
const args = node.arguments;
|
|
1736
|
+
if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
|
|
1737
|
+
return `make(${setType})`;
|
|
1738
|
+
}
|
|
1739
|
+
const initArg = args[0];
|
|
1740
|
+
const tmp = getTempName('set');
|
|
1741
|
+
const values = initArg.elements.map((el) => `${tmp}[${visit(el)}] = struct{}{}`).join('; ');
|
|
1742
|
+
return `func() ${setType} { ${tmp} := make(${setType}); ${values}; return ${tmp} }()`;
|
|
1743
|
+
}
|
|
1744
|
+
function specifierToGoFileName(specifier) {
|
|
1745
|
+
const segments = specifier.split(/[/\\]/);
|
|
1746
|
+
let name = segments[segments.length - 1] || segments[segments.length - 2] || 'import';
|
|
1747
|
+
// Strip all extensions (e.g. .spec.ts → '', .ts → '')
|
|
1748
|
+
name = name.replace(/(\.[^.]+)+$/, '');
|
|
1749
|
+
name = name.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
|
1750
|
+
if (!name)
|
|
1751
|
+
name = 'import';
|
|
1752
|
+
// Deduplicate filename if already taken by a different import
|
|
1753
|
+
let candidate = name + '.go';
|
|
1754
|
+
let i = 2;
|
|
1755
|
+
while (localImportFiles.has(candidate)) {
|
|
1756
|
+
candidate = `${name}_${i}.go`;
|
|
1757
|
+
i++;
|
|
1758
|
+
}
|
|
1759
|
+
return candidate;
|
|
1760
|
+
}
|
|
1761
|
+
function normalizeCjsToEsm(code) {
|
|
1762
|
+
// Remove 'use strict' directive
|
|
1763
|
+
code = code.replace(/['"]use strict['"];?\n?/g, '');
|
|
1764
|
+
// module.exports.X = function(...) { } or exports.X = function(...) { }
|
|
1765
|
+
code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
|
|
1766
|
+
return code;
|
|
1767
|
+
}
|
|
1768
|
+
function includeLocalImport(code, dir, goFileName) {
|
|
1769
|
+
const prevDir = currentFileDir;
|
|
1770
|
+
currentFileDir = dir;
|
|
1771
|
+
// Normalize CommonJS modules to ES module syntax before parsing
|
|
1772
|
+
if (!code.includes('export ') && (code.includes('module.exports') || code.includes('exports.'))) {
|
|
1773
|
+
code = normalizeCjsToEsm(code);
|
|
1774
|
+
}
|
|
1775
|
+
if (!goFileName) {
|
|
1776
|
+
// Inline mode (npm packages): pull declarations into the current transpilation context
|
|
1777
|
+
const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
1778
|
+
for (const stmt of sf.statements) {
|
|
1779
|
+
visit(stmt, { addFunctionOutside: true });
|
|
1780
|
+
}
|
|
1781
|
+
currentFileDir = prevDir;
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
// Separate-file mode (local TS imports): transpile to its own Go file
|
|
1785
|
+
const savedOutsideNodes = outsideNodes;
|
|
1786
|
+
const savedPackages = [...importedPackages];
|
|
1787
|
+
outsideNodes = [];
|
|
1788
|
+
importedPackages.clear();
|
|
1789
|
+
const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
1790
|
+
const inlineLines = [];
|
|
1791
|
+
for (const stmt of sf.statements) {
|
|
1792
|
+
const result = visit(stmt, { addFunctionOutside: true });
|
|
1793
|
+
if (result.trim())
|
|
1794
|
+
inlineLines.push(result);
|
|
1795
|
+
}
|
|
1796
|
+
const fileImports = [...importedPackages].map((pkg) => `import "${pkg}"`).join('\n');
|
|
1797
|
+
const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
1798
|
+
const fileInline = inlineLines.join('\n');
|
|
1799
|
+
const parts = ['package main'];
|
|
1800
|
+
if (fileImports)
|
|
1801
|
+
parts.push(fileImports);
|
|
1802
|
+
if (fileInline.trim())
|
|
1803
|
+
parts.push(fileInline.trim());
|
|
1804
|
+
if (fileOutside.trim())
|
|
1805
|
+
parts.push(fileOutside.trim());
|
|
1806
|
+
localImportFiles.set(goFileName, parts.join('\n\n'));
|
|
1807
|
+
// Restore main-file state
|
|
1808
|
+
outsideNodes = savedOutsideNodes;
|
|
1809
|
+
importedPackages.clear();
|
|
1810
|
+
for (const p of savedPackages)
|
|
1811
|
+
importedPackages.add(p);
|
|
1812
|
+
currentFileDir = prevDir;
|
|
1813
|
+
}
|
|
1814
|
+
function registerGoPackageAliases(node, goPkg) {
|
|
1815
|
+
const pkgName = goPkg.split('/').pop();
|
|
1816
|
+
if (!node.importClause)
|
|
1817
|
+
return;
|
|
1818
|
+
const clause = node.importClause;
|
|
1819
|
+
// Default import: `import fmt from 'go:fmt'` or `import myFmt from 'go:fmt'`
|
|
1820
|
+
if (clause.name && clause.name.text !== pkgName) {
|
|
1821
|
+
importAliases.set(clause.name.text, pkgName);
|
|
1822
|
+
}
|
|
1823
|
+
if (clause.namedBindings) {
|
|
1824
|
+
if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
1825
|
+
// `import * as myFmt from 'go:fmt'`
|
|
1826
|
+
const localName = clause.namedBindings.name.text;
|
|
1827
|
+
if (localName !== pkgName) {
|
|
1828
|
+
importAliases.set(localName, pkgName);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
else if (ts.isNamedImports(clause.namedBindings)) {
|
|
1832
|
+
// `import { Println, Sprintf as Spf } from 'go:fmt'`
|
|
1833
|
+
for (const el of clause.namedBindings.elements) {
|
|
1834
|
+
if (el.isTypeOnly)
|
|
1835
|
+
continue;
|
|
1836
|
+
const localName = el.name.text;
|
|
1837
|
+
const importedName = el.propertyName?.text ?? localName;
|
|
1838
|
+
importAliases.set(localName, `${pkgName}.${importedName}`);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
function getImportLocalName(node) {
|
|
1844
|
+
const clause = node.importClause;
|
|
1845
|
+
if (!clause)
|
|
1846
|
+
return null;
|
|
1847
|
+
if (clause.name)
|
|
1848
|
+
return clause.name.text;
|
|
1849
|
+
if (clause.namedBindings) {
|
|
1850
|
+
if (ts.isNamespaceImport(clause.namedBindings))
|
|
1851
|
+
return clause.namedBindings.name.text;
|
|
1852
|
+
}
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
1855
|
+
// Per-module table: maps Node.js function name → Go expression template.
|
|
1856
|
+
// For default/namespace imports (e.g. `import path from 'node:path'`), entries are registered
|
|
1857
|
+
// as `callHandlers[localName.funcName]`. For named imports (e.g. `import { join } from 'node:path'`),
|
|
1858
|
+
// the Go identifier is stored in importAliases so bare calls like `join(...)` resolve correctly.
|
|
1859
|
+
const nodeModuleMappings = {
|
|
1860
|
+
path: {
|
|
1861
|
+
goPackage: 'path/filepath',
|
|
1862
|
+
functions: {
|
|
1863
|
+
join: (args) => `filepath.Join(${args.join(', ')})`,
|
|
1864
|
+
dirname: (args) => `filepath.Dir(${args[0]})`,
|
|
1865
|
+
basename: (args) => args[1]
|
|
1866
|
+
? `strings.TrimSuffix(filepath.Base(${args[0]}), ${args[1]})`
|
|
1867
|
+
: `filepath.Base(${args[0]})`,
|
|
1868
|
+
extname: (args) => `filepath.Ext(${args[0]})`,
|
|
1869
|
+
resolve: (args) => `func() string { p, _ := filepath.Abs(filepath.Join(${args.join(', ')})); return p }()`
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
// Future node stdlib modules can be added here as additional keys:
|
|
1873
|
+
// fs: { goPackage: 'os', functions: { ... } },
|
|
1874
|
+
// os: { goPackage: 'os', functions: { ... } },
|
|
1875
|
+
};
|
|
1876
|
+
// Mapping from Node.js stdlib module names to Go setup functions.
|
|
1877
|
+
// Each entry adds the required Go imports and registers call handlers for the local identifier.
|
|
1878
|
+
function setupNodeModuleImport(node, nodeModule) {
|
|
1879
|
+
const mapping = nodeModuleMappings[nodeModule];
|
|
1880
|
+
if (!mapping)
|
|
1881
|
+
return;
|
|
1882
|
+
importedPackages.add(mapping.goPackage);
|
|
1883
|
+
const clause = node.importClause;
|
|
1884
|
+
if (!clause)
|
|
1885
|
+
return;
|
|
1886
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
1887
|
+
// Named imports: `import { join, dirname } from 'node:path'`
|
|
1888
|
+
// Map each local name directly to its Go qualified function via importAliases,
|
|
1889
|
+
// so bare calls like `join(...)` resolve to `filepath.Join(...)`.
|
|
1890
|
+
for (const el of clause.namedBindings.elements) {
|
|
1891
|
+
if (el.isTypeOnly)
|
|
1892
|
+
continue;
|
|
1893
|
+
const localName = el.name.text;
|
|
1894
|
+
const importedName = el.propertyName?.text ?? localName;
|
|
1895
|
+
const fn = mapping.functions[importedName];
|
|
1896
|
+
if (fn) {
|
|
1897
|
+
// Register a call handler keyed on the local name
|
|
1898
|
+
callHandlers[localName] = (_caller, args) => fn(args);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
else {
|
|
1903
|
+
// Default import (`import path from 'node:path'`) or
|
|
1904
|
+
// namespace import (`import * as path from 'node:path'`)
|
|
1905
|
+
const localName = getImportLocalName(node) ?? nodeModule;
|
|
1906
|
+
for (const [funcName, fn] of Object.entries(mapping.functions)) {
|
|
1907
|
+
callHandlers[`${localName}.${funcName}`] = (_caller, args) => fn(args);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
function visitImportDeclaration(node) {
|
|
1912
|
+
if (!ts.isStringLiteral(node.moduleSpecifier))
|
|
1913
|
+
return '';
|
|
1914
|
+
const moduleSpec = node.moduleSpecifier.text;
|
|
1915
|
+
// Relative imports → transpile to a separate Go file
|
|
1916
|
+
if (moduleSpec.startsWith('.') || moduleSpec.startsWith('/')) {
|
|
1917
|
+
// Key combines current dir + specifier to avoid cross-package collisions
|
|
1918
|
+
const key = `${currentFileDir ?? ''}::${moduleSpec}`;
|
|
1919
|
+
if (fileResolver && !includedLocalImports.has(key)) {
|
|
1920
|
+
includedLocalImports.add(key);
|
|
1921
|
+
const resolved = fileResolver(moduleSpec, currentFileDir);
|
|
1922
|
+
if (resolved) {
|
|
1923
|
+
const goFileName = specifierToGoFileName(moduleSpec);
|
|
1924
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return '';
|
|
1928
|
+
}
|
|
1929
|
+
// Type-only imports contribute nothing to runtime
|
|
1930
|
+
if (node.importClause?.isTypeOnly)
|
|
1931
|
+
return '';
|
|
1932
|
+
// Go standard library: `import { Println } from 'go:fmt'` → import "fmt"
|
|
1933
|
+
if (moduleSpec.startsWith('go:')) {
|
|
1934
|
+
const goPkg = moduleSpec.slice(3);
|
|
1935
|
+
importedPackages.add(goPkg);
|
|
1936
|
+
registerGoPackageAliases(node, goPkg);
|
|
1937
|
+
return '';
|
|
1938
|
+
}
|
|
1939
|
+
// Node.js standard library: `import path from 'node:path'` → mapped Go packages
|
|
1940
|
+
if (moduleSpec.startsWith('node:')) {
|
|
1941
|
+
const nodeModule = moduleSpec.slice(5);
|
|
1942
|
+
setupNodeModuleImport(node, nodeModule);
|
|
1943
|
+
return '';
|
|
1944
|
+
}
|
|
1945
|
+
// npm package (bare specifier) → transpile to its own Go file
|
|
1946
|
+
const npmKey = `npm::${moduleSpec}`;
|
|
1947
|
+
if (fileResolver && !includedLocalImports.has(npmKey)) {
|
|
1948
|
+
includedLocalImports.add(npmKey);
|
|
1949
|
+
const resolved = fileResolver(moduleSpec, currentFileDir);
|
|
1950
|
+
if (resolved) {
|
|
1951
|
+
const goFileName = specifierToGoFileName(moduleSpec);
|
|
1952
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return '';
|
|
1956
|
+
}
|
|
1957
|
+
function getForOfVarNames(initializer) {
|
|
1958
|
+
if (!ts.isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
|
|
1959
|
+
return ['_'];
|
|
1960
|
+
}
|
|
1961
|
+
const decl = initializer.declarations[0];
|
|
1962
|
+
if (ts.isArrayBindingPattern(decl.name)) {
|
|
1963
|
+
return decl.name.elements.map((el) => {
|
|
1964
|
+
if (ts.isOmittedExpression(el))
|
|
1965
|
+
return '_';
|
|
1966
|
+
return visit(el.name);
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
return [visit(decl.name)];
|
|
1970
|
+
}
|
|
1971
|
+
function visitTryStatement(node, options) {
|
|
1972
|
+
const deferreds = [];
|
|
1973
|
+
// Register finally first (LIFO: runs last after catch)
|
|
1974
|
+
if (node.finallyBlock) {
|
|
1975
|
+
const finallyBody = node.finallyBlock.statements.map((s) => visit(s)).join('\t');
|
|
1976
|
+
deferreds.push(`defer func() {\n\t\t\t${finallyBody}\t\t\t}()`);
|
|
1977
|
+
}
|
|
1978
|
+
// Register catch second (LIFO: runs first, handles panic via recover)
|
|
1979
|
+
if (node.catchClause) {
|
|
1980
|
+
const varDecl = node.catchClause.variableDeclaration;
|
|
1981
|
+
const catchVar = varDecl ? visit(varDecl.name) : '_r';
|
|
1982
|
+
const catchBody = node.catchClause.block.statements.map((s) => visit(s)).join('\t');
|
|
1983
|
+
deferreds.push(`defer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t${catchVar} := r\n\t\t\t\t_ = ${catchVar}\n\t\t\t\t${catchBody}\t\t\t}\n\t\t\t}()`);
|
|
1984
|
+
}
|
|
1985
|
+
const tryBody = node.tryBlock.statements.map((s) => visit(s)).join('\t');
|
|
1986
|
+
const body = [...deferreds, tryBody].join('\n\t\t\t');
|
|
1987
|
+
return `func() {\n\t\t\t${body}\n\t\t\t}()` + (options.inline ? '' : ';\n\t');
|
|
1988
|
+
}
|
package/go.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference path="types/typenative-go.d.ts" />
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference path="types/typenative.d.ts" />
|
package/npm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference path="types/typenative-npm.d.ts" />
|