typenative 0.0.18 → 0.0.20
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 +2 -0
- package/CHANGELOG.md +37 -0
- package/README.md +62 -33
- package/bin/index.js +255 -119
- package/bin/transpiler.js +656 -24
- package/go.d.ts +1 -0
- package/index.d.ts +1 -0
- package/npm.d.ts +1 -0
- package/package.json +14 -6
- package/types/typenative-go.d.ts +15 -0
- package/types/typenative-npm.d.ts +5 -0
- package/types/typenative.d.ts +102 -2
package/bin/transpiler.js
CHANGED
|
@@ -6,7 +6,16 @@ const importedPackages = new Set();
|
|
|
6
6
|
let outsideNodes = [];
|
|
7
7
|
const classNames = new Set();
|
|
8
8
|
let promiseResolveName = '';
|
|
9
|
-
|
|
9
|
+
// Go keywords that cannot be used as identifiers
|
|
10
|
+
const dangerousNames = new Set([
|
|
11
|
+
'main',
|
|
12
|
+
// Go reserved keywords
|
|
13
|
+
'break', 'case', 'chan', 'const', 'continue',
|
|
14
|
+
'default', 'defer', 'else', 'fallthrough', 'for',
|
|
15
|
+
'func', 'go', 'goto', 'if', 'import',
|
|
16
|
+
'interface', 'map', 'package', 'range', 'return',
|
|
17
|
+
'select', 'struct', 'switch', 'type', 'var',
|
|
18
|
+
]);
|
|
10
19
|
const renamedFunctions = new Map();
|
|
11
20
|
const variableTypes = new Map();
|
|
12
21
|
const variableGoTypes = new Map();
|
|
@@ -17,7 +26,29 @@ const interfacePropertyTypes = new Map();
|
|
|
17
26
|
const typeAliases = new Map();
|
|
18
27
|
const enumNames = new Set();
|
|
19
28
|
const enumBaseTypes = new Map();
|
|
20
|
-
|
|
29
|
+
// Maps local TS name → Go qualified name (e.g. 'Println' → 'fmt.Println', 'myFmt' → 'fmt')
|
|
30
|
+
const importAliases = new Map();
|
|
31
|
+
// Tracks static methods per class: Set of "ClassName.methodName" strings
|
|
32
|
+
const classStaticMethods = new Set();
|
|
33
|
+
// Tracks static properties per class: Set of "ClassName.propName" strings
|
|
34
|
+
const classStaticProps = new Set();
|
|
35
|
+
// Callback for resolving import specifiers to source code.
|
|
36
|
+
// specifier: the raw import string (relative path or package name)
|
|
37
|
+
// fromDir: directory of the file containing the import (null = main file's dir)
|
|
38
|
+
// Returns the file content and its directory (for resolving that file's own imports)
|
|
39
|
+
let fileResolver = null;
|
|
40
|
+
// Directory of the file currently being processed (null = main entry file)
|
|
41
|
+
let currentFileDir = null;
|
|
42
|
+
// Tracks already-included files by a stable key to prevent duplicates/cycles
|
|
43
|
+
const includedLocalImports = new Set();
|
|
44
|
+
// Collects Go source files generated from local TS imports (filename → content)
|
|
45
|
+
let localImportFiles = new Map();
|
|
46
|
+
// Default import namespaces from npm/local packages (e.g. `import ts from 'typescript'` → 'ts')
|
|
47
|
+
// Property accesses on these are stripped: ts.createSourceFile → createSourceFile
|
|
48
|
+
const defaultImportNamespaces = new Set();
|
|
49
|
+
export function transpileToNative(code, options) {
|
|
50
|
+
fileResolver = options?.readFile ?? null;
|
|
51
|
+
currentFileDir = null;
|
|
21
52
|
const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
22
53
|
TypeCheker = ts.createProgram(['main.ts'], {}).getTypeChecker();
|
|
23
54
|
importedPackages.clear();
|
|
@@ -34,17 +65,24 @@ export function transpileToNative(code) {
|
|
|
34
65
|
typeAliases.clear();
|
|
35
66
|
enumNames.clear();
|
|
36
67
|
enumBaseTypes.clear();
|
|
68
|
+
importAliases.clear();
|
|
69
|
+
includedLocalImports.clear();
|
|
70
|
+
localImportFiles = new Map();
|
|
71
|
+
defaultImportNamespaces.clear();
|
|
72
|
+
classStaticMethods.clear();
|
|
73
|
+
classStaticProps.clear();
|
|
37
74
|
const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
|
|
38
75
|
const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
39
|
-
|
|
76
|
+
const main = `package main
|
|
40
77
|
|
|
41
78
|
${[...importedPackages].map((pkg) => `import "${pkg}"`).join('\n')}
|
|
42
79
|
|
|
43
80
|
func main() {
|
|
44
81
|
${transpiledCode.trim()}
|
|
45
82
|
}
|
|
46
|
-
|
|
83
|
+
|
|
47
84
|
${transpiledCodeOutside.trim()}`;
|
|
85
|
+
return { main, files: localImportFiles };
|
|
48
86
|
}
|
|
49
87
|
export function visit(node, options = {}) {
|
|
50
88
|
let code = '';
|
|
@@ -57,6 +95,9 @@ export function visit(node, options = {}) {
|
|
|
57
95
|
else if (ts.isIdentifier(node)) {
|
|
58
96
|
if (node.text === 'undefined')
|
|
59
97
|
return 'nil';
|
|
98
|
+
const goAlias = importAliases.get(node.text);
|
|
99
|
+
if (goAlias)
|
|
100
|
+
return goAlias;
|
|
60
101
|
return getSafeName(node.text);
|
|
61
102
|
}
|
|
62
103
|
else if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
@@ -95,6 +136,10 @@ export function visit(node, options = {}) {
|
|
|
95
136
|
}
|
|
96
137
|
else if (ts.isArrayLiteralExpression(node)) {
|
|
97
138
|
const type = ts.isVariableDeclaration(node.parent) ? getType(node.parent.type, true) : '';
|
|
139
|
+
const hasSpread = node.elements.some((e) => ts.isSpreadElement(e));
|
|
140
|
+
if (hasSpread) {
|
|
141
|
+
return visitSpreadArrayLiteral(node, type);
|
|
142
|
+
}
|
|
98
143
|
return `[]${type} {${node.elements.map((e) => visit(e)).join(', ')}}`;
|
|
99
144
|
}
|
|
100
145
|
else if (ts.isBlock(node)) {
|
|
@@ -115,12 +160,57 @@ export function visit(node, options = {}) {
|
|
|
115
160
|
if (ts.isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
|
|
116
161
|
return `${getSafeName(node.expression.text)}_${getEnumMemberName(node.name)}`;
|
|
117
162
|
}
|
|
163
|
+
// Strip default import namespace: `ts.createSourceFile` → `createSourceFile`
|
|
164
|
+
if (ts.isIdentifier(node.expression) && defaultImportNamespaces.has(node.expression.text)) {
|
|
165
|
+
return visit(node.name);
|
|
166
|
+
}
|
|
167
|
+
// Static member access: `Counter.count` → `Counter_count`
|
|
168
|
+
if (ts.isIdentifier(node.expression)) {
|
|
169
|
+
const key = `${node.expression.text}.${node.name.text}`;
|
|
170
|
+
if (classStaticMethods.has(key) || classStaticProps.has(key)) {
|
|
171
|
+
return `${node.expression.text}_${node.name.text}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
118
174
|
const leftSide = visit(node.expression);
|
|
119
175
|
const rightSide = visit(node.name);
|
|
120
176
|
const objectType = resolveExpressionType(node.expression);
|
|
121
177
|
return getAcessString(leftSide, rightSide, objectType);
|
|
122
178
|
}
|
|
123
179
|
else if (ts.isVariableDeclaration(node)) {
|
|
180
|
+
// Object destructuring: const { x, y } = obj
|
|
181
|
+
if (ts.isObjectBindingPattern(node.name) && node.initializer) {
|
|
182
|
+
const initExpr = visit(node.initializer);
|
|
183
|
+
const parts = node.name.elements.map((el) => {
|
|
184
|
+
const localName = visit(el.name);
|
|
185
|
+
const propName = el.propertyName ? visit(el.propertyName) : localName;
|
|
186
|
+
const defaultVal = el.initializer ? visit(el.initializer) : undefined;
|
|
187
|
+
if (defaultVal) {
|
|
188
|
+
return `${localName} := func() interface{} { if ${initExpr}.${propName} == nil { return ${defaultVal} }; return ${initExpr}.${propName} }()`;
|
|
189
|
+
}
|
|
190
|
+
return `${localName} := ${initExpr}.${propName}`;
|
|
191
|
+
});
|
|
192
|
+
return parts.join(';\n\t');
|
|
193
|
+
}
|
|
194
|
+
// Array destructuring: const [a, b] = arr
|
|
195
|
+
if (ts.isArrayBindingPattern(node.name) && node.initializer) {
|
|
196
|
+
const initExpr = visit(node.initializer);
|
|
197
|
+
const tmpVar = getTempName('arr');
|
|
198
|
+
const parts = [`${tmpVar} := ${initExpr}`];
|
|
199
|
+
node.name.elements.forEach((el, idx) => {
|
|
200
|
+
if (ts.isOmittedExpression(el))
|
|
201
|
+
return;
|
|
202
|
+
const bindEl = el;
|
|
203
|
+
const localName = visit(bindEl.name);
|
|
204
|
+
// Variables starting with _ are intentionally unused — use blank identifier
|
|
205
|
+
if (localName === '_' || localName.startsWith('_')) {
|
|
206
|
+
parts.push(`_ = ${tmpVar}[${idx}]`);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
parts.push(`${localName} := ${tmpVar}[${idx}]`);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return parts.join(';\n\t');
|
|
213
|
+
}
|
|
124
214
|
const type = getType(node.type);
|
|
125
215
|
// Track variable type for type-aware method dispatch
|
|
126
216
|
if (ts.isIdentifier(node.name)) {
|
|
@@ -175,6 +265,19 @@ export function visit(node, options = {}) {
|
|
|
175
265
|
(ts.isPropertyAccessExpression(node.expression) && hasQuestionDot(node.expression))) {
|
|
176
266
|
return visitOptionalCall(node);
|
|
177
267
|
}
|
|
268
|
+
// IIFE with named function expression: (function name() { ... })()
|
|
269
|
+
if (ts.isParenthesizedExpression(node.expression) &&
|
|
270
|
+
ts.isFunctionExpression(node.expression.expression)) {
|
|
271
|
+
const fn = node.expression.expression;
|
|
272
|
+
const parameterInfo = getFunctionParametersInfo(fn.parameters);
|
|
273
|
+
if (fn.body && ts.isBlock(fn.body)) {
|
|
274
|
+
prescanVariableDeclarations(fn.body);
|
|
275
|
+
}
|
|
276
|
+
const inferredRetType = inferFunctionBodyReturnType(fn);
|
|
277
|
+
const returnType = inferredRetType ? ` ${inferredRetType}` : '';
|
|
278
|
+
const args = node.arguments.map((a) => visit(a)).join(', ');
|
|
279
|
+
return `func(${parameterInfo.signature})${returnType} ${visit(fn.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}(${args})`;
|
|
280
|
+
}
|
|
178
281
|
// Handle setTimeout specially to get raw delay value
|
|
179
282
|
if (ts.isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
|
|
180
283
|
importedPackages.add('time');
|
|
@@ -190,7 +293,11 @@ export function visit(node, options = {}) {
|
|
|
190
293
|
const caller = visit(node.expression);
|
|
191
294
|
const safeCaller = getSafeName(caller);
|
|
192
295
|
const typeArgs = getTypeArguments(node.typeArguments);
|
|
193
|
-
|
|
296
|
+
// Handle spread arguments: fn(...arr) → fn(arr...)
|
|
297
|
+
const hasSpreadArg = node.arguments.some((a) => ts.isSpreadElement(a));
|
|
298
|
+
const args = hasSpreadArg
|
|
299
|
+
? node.arguments.map((a) => ts.isSpreadElement(a) ? `${visit(a.expression)}...` : visit(a))
|
|
300
|
+
: node.arguments.map((a) => visit(a));
|
|
194
301
|
// Resolve object type for type-aware method dispatch
|
|
195
302
|
let objectType;
|
|
196
303
|
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
@@ -236,9 +343,25 @@ export function visit(node, options = {}) {
|
|
|
236
343
|
inline: true
|
|
237
344
|
})}; ${visit(node.incrementor, { inline: true })}${visit(node.statement)}`;
|
|
238
345
|
}
|
|
346
|
+
else if (ts.isForInStatement(node)) {
|
|
347
|
+
const varName = ts.isVariableDeclarationList(node.initializer)
|
|
348
|
+
? visit(node.initializer.declarations[0].name)
|
|
349
|
+
: visit(node.initializer);
|
|
350
|
+
return `for ${varName} := range ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
|
|
351
|
+
}
|
|
239
352
|
else if (ts.isForOfStatement(node)) {
|
|
240
|
-
|
|
241
|
-
|
|
353
|
+
// Unwrap Object.entries(x) → treat x as the iterable
|
|
354
|
+
let iterNode = node.expression;
|
|
355
|
+
if (ts.isCallExpression(iterNode) &&
|
|
356
|
+
ts.isPropertyAccessExpression(iterNode.expression) &&
|
|
357
|
+
ts.isIdentifier(iterNode.expression.expression) &&
|
|
358
|
+
iterNode.expression.expression.text === 'Object' &&
|
|
359
|
+
iterNode.expression.name.text === 'entries' &&
|
|
360
|
+
iterNode.arguments.length > 0) {
|
|
361
|
+
iterNode = iterNode.arguments[0];
|
|
362
|
+
}
|
|
363
|
+
const iterExpr = visit(iterNode, { inline: true });
|
|
364
|
+
const iterType = inferExpressionType(iterNode);
|
|
242
365
|
if (iterType && iterType.startsWith('map[')) {
|
|
243
366
|
const valueType = extractMapValueType(iterType);
|
|
244
367
|
const isSet = valueType === 'struct{}';
|
|
@@ -436,11 +559,23 @@ export function visit(node, options = {}) {
|
|
|
436
559
|
const properties = new Map();
|
|
437
560
|
const methods = new Map();
|
|
438
561
|
for (const member of node.members) {
|
|
562
|
+
const memberModifiers = member.modifiers;
|
|
563
|
+
const isStatic = memberModifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
439
564
|
if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
|
|
440
|
-
|
|
565
|
+
if (isStatic) {
|
|
566
|
+
classStaticProps.add(`${className}.${member.name.text}`);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
|
|
570
|
+
}
|
|
441
571
|
}
|
|
442
572
|
if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
|
|
443
|
-
|
|
573
|
+
if (isStatic) {
|
|
574
|
+
classStaticMethods.add(`${className}.${member.name.text}`);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
methods.set(member.name.text, member.type ? getType(member.type) : 'interface{}');
|
|
578
|
+
}
|
|
444
579
|
}
|
|
445
580
|
}
|
|
446
581
|
classPropertyTypes.set(className, properties);
|
|
@@ -498,9 +633,36 @@ export function visit(node, options = {}) {
|
|
|
498
633
|
const methodName = visit(member.name);
|
|
499
634
|
const methodParameterInfo = getFunctionParametersInfo(member.parameters);
|
|
500
635
|
const returnType = member.type ? ` ${getType(member.type)}` : '';
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
636
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
637
|
+
if (isStatic) {
|
|
638
|
+
// Static methods become package-level functions named ClassName_methodName
|
|
639
|
+
result += `func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
result += `func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
else if (ts.isGetAccessor(member)) {
|
|
646
|
+
// Getter: get prop() { ... } → func (self *T) Prop() RetType { ... }
|
|
647
|
+
const getterName = visit(member.name);
|
|
648
|
+
const returnType = member.type ? ` ${getType(member.type)}` : ' interface{}';
|
|
649
|
+
result += `func (self *${name}${typeParamNames}) Get_${getterName}()${returnType} ${visit(member.body)}\n\n`;
|
|
650
|
+
}
|
|
651
|
+
else if (ts.isSetAccessor(member)) {
|
|
652
|
+
// Setter: set prop(val) { ... } → func (self *T) SetProp(val ValType) { ... }
|
|
653
|
+
const setterName = visit(member.name);
|
|
654
|
+
const parameterInfo = getFunctionParametersInfo(member.parameters);
|
|
655
|
+
result += `func (self *${name}${typeParamNames}) Set_${setterName}(${parameterInfo.signature}) ${visit(member.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}\n\n`;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Static property declarations → package-level vars named ClassName_propName
|
|
659
|
+
for (const member of node.members) {
|
|
660
|
+
if (ts.isPropertyDeclaration(member) &&
|
|
661
|
+
member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword)) {
|
|
662
|
+
const fieldName = visit(member.name);
|
|
663
|
+
const fieldType = getOptionalNodeType(member.type, !!member.questionToken);
|
|
664
|
+
const initializer = member.initializer ? ` = ${visit(member.initializer)}` : '';
|
|
665
|
+
result += `var ${name}_${fieldName} ${fieldType}${initializer}\n\n`;
|
|
504
666
|
}
|
|
505
667
|
}
|
|
506
668
|
return result.trim();
|
|
@@ -543,6 +705,12 @@ export function visit(node, options = {}) {
|
|
|
543
705
|
if (ts.isPropertyAssignment(p)) {
|
|
544
706
|
return `${visit(p.name)}: ${visit(p.initializer)}`;
|
|
545
707
|
}
|
|
708
|
+
// Shorthand: { name } → name: name
|
|
709
|
+
if (ts.isShorthandPropertyAssignment(p)) {
|
|
710
|
+
const name = visit(p.name);
|
|
711
|
+
return `${name}: ${name}`;
|
|
712
|
+
}
|
|
713
|
+
// Spread: { ...obj } — not easily supported in Go structs, omit
|
|
546
714
|
return '';
|
|
547
715
|
})
|
|
548
716
|
.filter((p) => p)
|
|
@@ -555,9 +723,16 @@ export function visit(node, options = {}) {
|
|
|
555
723
|
else if (ts.isNonNullExpression(node)) {
|
|
556
724
|
return visit(node.expression);
|
|
557
725
|
}
|
|
726
|
+
else if (ts.isImportDeclaration(node)) {
|
|
727
|
+
return visitImportDeclaration(node);
|
|
728
|
+
}
|
|
729
|
+
else if (ts.isExportDeclaration(node) || ts.isExportAssignment(node)) {
|
|
730
|
+
return '';
|
|
731
|
+
}
|
|
558
732
|
const syntaxKind = ts.SyntaxKind[node.kind];
|
|
559
733
|
if (!['FirstStatement', 'EndOfFileToken'].includes(syntaxKind)) {
|
|
560
|
-
|
|
734
|
+
const snippet = node.getText().substring(0, 60).replace(/\n/g, ' ');
|
|
735
|
+
console.warn(`[TypeNative] Unsupported syntax: ${syntaxKind} — "${snippet}"`);
|
|
561
736
|
}
|
|
562
737
|
ts.forEachChild(node, (subNode) => {
|
|
563
738
|
code += visit(subNode);
|
|
@@ -575,6 +750,34 @@ function getTypeText(typeNode) {
|
|
|
575
750
|
function toGoStringLiteral(value) {
|
|
576
751
|
return JSON.stringify(value);
|
|
577
752
|
}
|
|
753
|
+
function visitSpreadArrayLiteral(node, elemType) {
|
|
754
|
+
const chunks = [];
|
|
755
|
+
for (const el of node.elements) {
|
|
756
|
+
if (ts.isSpreadElement(el)) {
|
|
757
|
+
chunks.push({ isSpread: true, items: [el.expression] });
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
const last = chunks[chunks.length - 1];
|
|
761
|
+
if (last && !last.isSpread) {
|
|
762
|
+
last.items.push(el);
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
chunks.push({ isSpread: false, items: [el] });
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const baseType = elemType || 'interface{}';
|
|
770
|
+
let result = `[]${baseType}{}`;
|
|
771
|
+
for (const chunk of chunks) {
|
|
772
|
+
if (chunk.isSpread) {
|
|
773
|
+
result = `append(${result}, ${visit(chunk.items[0])}...)`;
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
result = `append(${result}, ${chunk.items.map((e) => visit(e)).join(', ')})`;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
578
781
|
function visitTemplateExpression(node) {
|
|
579
782
|
const parts = [];
|
|
580
783
|
if (node.head.text.length > 0) {
|
|
@@ -656,9 +859,7 @@ function inferFunctionBodyReturnType(node) {
|
|
|
656
859
|
return undefined;
|
|
657
860
|
}
|
|
658
861
|
function inferArrowFunctionGoType(node) {
|
|
659
|
-
const params = node.parameters
|
|
660
|
-
.map((p) => (p.type ? getType(p.type) : 'interface{}'))
|
|
661
|
-
.join(', ');
|
|
862
|
+
const params = node.parameters.map((p) => (p.type ? getType(p.type) : 'interface{}')).join(', ');
|
|
662
863
|
const retType = inferFunctionBodyReturnType(node);
|
|
663
864
|
return `func(${params})${retType ? ` ${retType}` : ''}`;
|
|
664
865
|
}
|
|
@@ -937,8 +1138,8 @@ function buildArrayCallbackInfo(callback, elementType, forcedReturnType) {
|
|
|
937
1138
|
params.push(`${visit(callback.parameters[2].name)} []${elementType}`);
|
|
938
1139
|
}
|
|
939
1140
|
const body = ts.isBlock(callback.body)
|
|
940
|
-
? visit(callback.body)
|
|
941
|
-
: `{
|
|
1141
|
+
? visit(callback.body, { inline: true })
|
|
1142
|
+
: `{ return ${visit(callback.body)}; }`;
|
|
942
1143
|
return {
|
|
943
1144
|
fnExpr: `func(${params.join(', ')}) ${callbackReturnType} ${body}`,
|
|
944
1145
|
paramCount,
|
|
@@ -961,11 +1162,24 @@ function buildArrayCallbackInvocation(callbackInfo, itemVar, indexVar, arrayVar)
|
|
|
961
1162
|
args.push(arrayVar);
|
|
962
1163
|
return `(${callbackInfo.fnExpr})(${args.join(', ')})`;
|
|
963
1164
|
}
|
|
1165
|
+
// Like buildArrayCallbackInvocation but for reduce: (acc, item, idx, arr)
|
|
1166
|
+
function buildArrayCallbackInvocationReduce(callbackInfo, accVar, itemVar, indexVar, arrayVar) {
|
|
1167
|
+
const args = [];
|
|
1168
|
+
if (callbackInfo.paramCount > 0)
|
|
1169
|
+
args.push(accVar);
|
|
1170
|
+
if (callbackInfo.paramCount > 1)
|
|
1171
|
+
args.push(itemVar);
|
|
1172
|
+
if (callbackInfo.paramCount > 2)
|
|
1173
|
+
args.push(`float64(${indexVar})`);
|
|
1174
|
+
if (callbackInfo.paramCount > 3)
|
|
1175
|
+
args.push(arrayVar);
|
|
1176
|
+
return `(${callbackInfo.fnExpr})(${args.join(', ')})`;
|
|
1177
|
+
}
|
|
964
1178
|
function visitArrayHigherOrderCall(node) {
|
|
965
1179
|
if (!ts.isPropertyAccessExpression(node.expression))
|
|
966
1180
|
return undefined;
|
|
967
1181
|
const methodName = node.expression.name.text;
|
|
968
|
-
if (!['map', 'filter', 'some', 'find', 'join'].includes(methodName)) {
|
|
1182
|
+
if (!['map', 'filter', 'some', 'find', 'findIndex', 'every', 'forEach', 'reduce', 'join'].includes(methodName)) {
|
|
969
1183
|
return undefined;
|
|
970
1184
|
}
|
|
971
1185
|
const arrayExprNode = node.expression.expression;
|
|
@@ -975,6 +1189,9 @@ function visitArrayHigherOrderCall(node) {
|
|
|
975
1189
|
? getArrayElementTypeFromGoType(ownerType)
|
|
976
1190
|
: 'interface{}';
|
|
977
1191
|
if (methodName === 'join') {
|
|
1192
|
+
// Only intercept array.join — if owner type is unknown/not array, fall through to callHandlers
|
|
1193
|
+
if (!isArrayLikeGoType(ownerType))
|
|
1194
|
+
return undefined;
|
|
978
1195
|
importedPackages.add('strings');
|
|
979
1196
|
importedPackages.add('fmt');
|
|
980
1197
|
const separator = node.arguments[0] ? visit(node.arguments[0]) : '""';
|
|
@@ -1010,6 +1227,43 @@ function visitArrayHigherOrderCall(node) {
|
|
|
1010
1227
|
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
1011
1228
|
return `func() bool { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return true } }; return false }()`;
|
|
1012
1229
|
}
|
|
1230
|
+
if (methodName === 'findIndex') {
|
|
1231
|
+
const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
|
|
1232
|
+
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
1233
|
+
return `func() float64 { ${arrVar} := ${arrayExpr}; for ${idxVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return float64(${idxVar}) } }; return float64(-1) }()`;
|
|
1234
|
+
}
|
|
1235
|
+
if (methodName === 'every') {
|
|
1236
|
+
const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
|
|
1237
|
+
const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
|
|
1238
|
+
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
1239
|
+
return `func() bool { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if !(${callbackCall}) { return false } }; return true }()`;
|
|
1240
|
+
}
|
|
1241
|
+
if (methodName === 'forEach') {
|
|
1242
|
+
const callbackInfo = buildArrayCallbackInfo(callback, elementType, '');
|
|
1243
|
+
const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
|
|
1244
|
+
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
1245
|
+
return `func() { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${callbackCall} } }()`;
|
|
1246
|
+
}
|
|
1247
|
+
if (methodName === 'reduce') {
|
|
1248
|
+
const reduceCallback = node.arguments[0];
|
|
1249
|
+
if (!reduceCallback)
|
|
1250
|
+
return undefined;
|
|
1251
|
+
const initialValue = node.arguments[1] ? visit(node.arguments[1]) : undefined;
|
|
1252
|
+
const accType = initialValue
|
|
1253
|
+
? (inferExpressionType(node.arguments[1]) ?? elementType)
|
|
1254
|
+
: elementType;
|
|
1255
|
+
const accVar = getTempName('acc');
|
|
1256
|
+
// Build a callback that takes (acc, item, idx, arr) — param count determines what gets passed
|
|
1257
|
+
const cbInfo = buildArrayCallbackInfo(reduceCallback, elementType, accType ?? 'interface{}');
|
|
1258
|
+
const cbCall = buildArrayCallbackInvocationReduce(cbInfo, accVar, itemVar, idxVar, arrVar);
|
|
1259
|
+
// Only include the index loop var if the callback uses it (paramCount > 2)
|
|
1260
|
+
const reduceIdxVar = cbInfo.paramCount > 2 ? idxVar : '_';
|
|
1261
|
+
if (initialValue !== undefined) {
|
|
1262
|
+
return `func() ${accType} { ${arrVar} := ${arrayExpr}; ${accVar} := ${initialValue}; for ${reduceIdxVar}, ${itemVar} := range ${arrVar} { ${accVar} = ${cbCall} }; return ${accVar} }()`;
|
|
1263
|
+
}
|
|
1264
|
+
return `func() ${elementType} { ${arrVar} := ${arrayExpr}; ${accVar} := ${arrVar}[0]; for ${reduceIdxVar}, ${itemVar} := range ${arrVar}[1:] { ${accVar} = ${cbCall} }; return ${accVar} }()`;
|
|
1265
|
+
}
|
|
1266
|
+
// find
|
|
1013
1267
|
const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
|
|
1014
1268
|
const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
|
|
1015
1269
|
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
@@ -1263,6 +1517,21 @@ function getAcessString(leftSide, rightSide, objectType) {
|
|
|
1263
1517
|
if (rightSide === 'size' && (objectType === 'Map' || objectType === 'Set')) {
|
|
1264
1518
|
return `float64(len(${leftSide}))`;
|
|
1265
1519
|
}
|
|
1520
|
+
// process global properties
|
|
1521
|
+
if (leftSide === 'process') {
|
|
1522
|
+
if (rightSide === 'argv') {
|
|
1523
|
+
importedPackages.add('os');
|
|
1524
|
+
return 'os.Args';
|
|
1525
|
+
}
|
|
1526
|
+
if (rightSide === 'platform') {
|
|
1527
|
+
importedPackages.add('runtime');
|
|
1528
|
+
return 'runtime.GOOS';
|
|
1529
|
+
}
|
|
1530
|
+
if (rightSide === 'env') {
|
|
1531
|
+
// process.env.X is handled by nested property access; just return a placeholder object
|
|
1532
|
+
return 'os.Environ()';
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1266
1535
|
return `${leftSide}.${rightSide}`;
|
|
1267
1536
|
}
|
|
1268
1537
|
const callHandlers = {
|
|
@@ -1326,7 +1595,84 @@ const callHandlers = {
|
|
|
1326
1595
|
parseFloat: (_caller, args) => {
|
|
1327
1596
|
importedPackages.add('strconv');
|
|
1328
1597
|
return `func() float64 { v, _ := strconv.ParseFloat(${args[0]}, 64); return v }()`;
|
|
1329
|
-
}
|
|
1598
|
+
},
|
|
1599
|
+
'process.exit': (_caller, args) => {
|
|
1600
|
+
importedPackages.add('os');
|
|
1601
|
+
return `os.Exit(int(${args[0] ?? '0'}))`;
|
|
1602
|
+
},
|
|
1603
|
+
'process.cwd': () => {
|
|
1604
|
+
importedPackages.add('os');
|
|
1605
|
+
return `func() string { __d, _ := os.Getwd(); return __d }()`;
|
|
1606
|
+
},
|
|
1607
|
+
'JSON.stringify': (_caller, args) => {
|
|
1608
|
+
importedPackages.add('encoding/json');
|
|
1609
|
+
if (args.length >= 3) {
|
|
1610
|
+
return `func() string { __b, _ := json.MarshalIndent(${args[0]}, "", " "); return string(__b) }()`;
|
|
1611
|
+
}
|
|
1612
|
+
return `func() string { __b, _ := json.Marshal(${args[0]}); return string(__b) }()`;
|
|
1613
|
+
},
|
|
1614
|
+
'JSON.parse': (_caller, args) => {
|
|
1615
|
+
importedPackages.add('encoding/json');
|
|
1616
|
+
return `func() interface{} { var __v interface{}; json.Unmarshal([]byte(${args[0]}), &__v); return __v }()`;
|
|
1617
|
+
},
|
|
1618
|
+
'Object.keys': (_caller, args) => {
|
|
1619
|
+
importedPackages.add('maps');
|
|
1620
|
+
return `func() []string { __k := make([]string, 0); for k := range ${args[0]} { __k = append(__k, k) }; return __k }()`;
|
|
1621
|
+
},
|
|
1622
|
+
'Object.values': (_caller, args) => {
|
|
1623
|
+
return `func() []interface{} { __v := make([]interface{}, 0); for _, v := range ${args[0]} { __v = append(__v, v) }; return __v }()`;
|
|
1624
|
+
},
|
|
1625
|
+
'Object.entries': (_caller, args) => {
|
|
1626
|
+
// When used outside for...of, produce a slice of [key, value] pairs — rarely needed
|
|
1627
|
+
return args[0];
|
|
1628
|
+
},
|
|
1629
|
+
'Math.log': (_caller, args) => {
|
|
1630
|
+
importedPackages.add('math');
|
|
1631
|
+
return `math.Log(${args[0]})`;
|
|
1632
|
+
},
|
|
1633
|
+
'Math.log2': (_caller, args) => {
|
|
1634
|
+
importedPackages.add('math');
|
|
1635
|
+
return `math.Log2(${args[0]})`;
|
|
1636
|
+
},
|
|
1637
|
+
'Math.log10': (_caller, args) => {
|
|
1638
|
+
importedPackages.add('math');
|
|
1639
|
+
return `math.Log10(${args[0]})`;
|
|
1640
|
+
},
|
|
1641
|
+
'Math.sin': (_caller, args) => {
|
|
1642
|
+
importedPackages.add('math');
|
|
1643
|
+
return `math.Sin(${args[0]})`;
|
|
1644
|
+
},
|
|
1645
|
+
'Math.cos': (_caller, args) => {
|
|
1646
|
+
importedPackages.add('math');
|
|
1647
|
+
return `math.Cos(${args[0]})`;
|
|
1648
|
+
},
|
|
1649
|
+
'Math.tan': (_caller, args) => {
|
|
1650
|
+
importedPackages.add('math');
|
|
1651
|
+
return `math.Tan(${args[0]})`;
|
|
1652
|
+
},
|
|
1653
|
+
'Math.trunc': (_caller, args) => {
|
|
1654
|
+
importedPackages.add('math');
|
|
1655
|
+
return `math.Trunc(${args[0]})`;
|
|
1656
|
+
},
|
|
1657
|
+
'Math.sign': (_caller, args) => {
|
|
1658
|
+
return `func() float64 { if ${args[0]} > 0 { return 1 }; if ${args[0]} < 0 { return -1 }; return 0 }()`;
|
|
1659
|
+
},
|
|
1660
|
+
'console.error': (_caller, args) => {
|
|
1661
|
+
importedPackages.add('fmt');
|
|
1662
|
+
importedPackages.add('os');
|
|
1663
|
+
return `fmt.Fprintln(os.Stderr, ${args.join(', ')})`;
|
|
1664
|
+
},
|
|
1665
|
+
'String': (_caller, args) => {
|
|
1666
|
+
importedPackages.add('fmt');
|
|
1667
|
+
return `fmt.Sprintf("%v", ${args[0]})`;
|
|
1668
|
+
},
|
|
1669
|
+
'Number': (_caller, args) => {
|
|
1670
|
+
importedPackages.add('strconv');
|
|
1671
|
+
return `func() float64 { v, _ := strconv.ParseFloat(fmt.Sprintf("%v", ${args[0]}), 64); return v }()`;
|
|
1672
|
+
},
|
|
1673
|
+
'Boolean': (_caller, args) => {
|
|
1674
|
+
return `(${args[0]} != nil && ${args[0]} != false && ${args[0]} != 0 && ${args[0]} != "")`;
|
|
1675
|
+
},
|
|
1330
1676
|
};
|
|
1331
1677
|
const stringMethodHandlers = {
|
|
1332
1678
|
split: (obj, args) => {
|
|
@@ -1393,6 +1739,32 @@ const stringMethodHandlers = {
|
|
|
1393
1739
|
return `${obj}[int(${args[0]}):]`;
|
|
1394
1740
|
},
|
|
1395
1741
|
concat: (obj, args) => `${obj} + ${args.join(' + ')}`,
|
|
1742
|
+
padStart: (obj, args) => {
|
|
1743
|
+
importedPackages.add('fmt');
|
|
1744
|
+
importedPackages.add('strings');
|
|
1745
|
+
const pad = args[1] ?? '" "';
|
|
1746
|
+
return `func() string { __s := ${obj}; __n := int(${args[0]}) - len(__s); if __n > 0 { __s = strings.Repeat(${pad}, __n)[:__n] + __s }; return __s }()`;
|
|
1747
|
+
},
|
|
1748
|
+
padEnd: (obj, args) => {
|
|
1749
|
+
importedPackages.add('strings');
|
|
1750
|
+
const pad = args[1] ?? '" "';
|
|
1751
|
+
return `func() string { __s := ${obj}; __n := int(${args[0]}) - len(__s); if __n > 0 { __s = __s + strings.Repeat(${pad}, __n)[:__n] }; return __s }()`;
|
|
1752
|
+
},
|
|
1753
|
+
match: (obj, args) => {
|
|
1754
|
+
importedPackages.add('regexp');
|
|
1755
|
+
return `regexp.MustCompile(${args[0]}).FindStringSubmatch(${obj})`;
|
|
1756
|
+
},
|
|
1757
|
+
matchAll: (obj, args) => {
|
|
1758
|
+
importedPackages.add('regexp');
|
|
1759
|
+
return `regexp.MustCompile(${args[0]}).FindAllStringSubmatch(${obj}, -1)`;
|
|
1760
|
+
},
|
|
1761
|
+
search: (obj, args) => {
|
|
1762
|
+
importedPackages.add('regexp');
|
|
1763
|
+
return `float64(regexp.MustCompile(${args[0]}).FindStringIndex(${obj})[0])`;
|
|
1764
|
+
},
|
|
1765
|
+
at: (obj, args) => {
|
|
1766
|
+
return `func() string { __i := int(${args[0]}); if __i < 0 { __i = len(${obj}) + __i }; return string(${obj}[__i]) }()`;
|
|
1767
|
+
},
|
|
1396
1768
|
toString: (obj) => {
|
|
1397
1769
|
importedPackages.add('fmt');
|
|
1398
1770
|
return `fmt.Sprintf("%v", ${obj})`;
|
|
@@ -1404,6 +1776,13 @@ const regexpMethodHandlers = {
|
|
|
1404
1776
|
};
|
|
1405
1777
|
const arrayMethodHandlers = {
|
|
1406
1778
|
push: (obj, args) => `${obj} = append(${obj}, ${args.join(', ')})`,
|
|
1779
|
+
pop: (obj) => {
|
|
1780
|
+
return `func() interface{} { if len(${obj}) == 0 { return nil }; __last := ${obj}[len(${obj})-1]; ${obj} = ${obj}[:len(${obj})-1]; return __last }()`;
|
|
1781
|
+
},
|
|
1782
|
+
shift: (obj) => {
|
|
1783
|
+
return `func() interface{} { if len(${obj}) == 0 { return nil }; __first := ${obj}[0]; ${obj} = ${obj}[1:]; return __first }()`;
|
|
1784
|
+
},
|
|
1785
|
+
unshift: (obj, args) => `${obj} = append([]interface{}{${args.join(', ')}}, ${obj}...)`,
|
|
1407
1786
|
join: (obj, args) => {
|
|
1408
1787
|
importedPackages.add('strings');
|
|
1409
1788
|
return `strings.Join(${obj}, ${args[0] ?? '""'})`;
|
|
@@ -1413,10 +1792,30 @@ const arrayMethodHandlers = {
|
|
|
1413
1792
|
return `${obj}[int(${args[0]}):int(${args[1]})]`;
|
|
1414
1793
|
return `${obj}[int(${args[0]}):]`;
|
|
1415
1794
|
},
|
|
1795
|
+
reverse: (obj) => {
|
|
1796
|
+
importedPackages.add('slices');
|
|
1797
|
+
return `func() interface{} { slices.Reverse(${obj}); return nil }()`;
|
|
1798
|
+
},
|
|
1799
|
+
sort: (obj) => {
|
|
1800
|
+
importedPackages.add('sort');
|
|
1801
|
+
return `func() interface{} { sort.Slice(${obj}, func(i, j int) bool { return fmt.Sprintf("%v", ${obj}[i]) < fmt.Sprintf("%v", ${obj}[j]) }); return nil }()`;
|
|
1802
|
+
},
|
|
1803
|
+
indexOf: (obj, args) => {
|
|
1804
|
+
return `func() float64 { for __i, __v := range ${obj} { if fmt.Sprintf("%v", __v) == fmt.Sprintf("%v", ${args[0]}) { return float64(__i) } }; return float64(-1) }()`;
|
|
1805
|
+
},
|
|
1806
|
+
includes: (obj, args) => {
|
|
1807
|
+
return `func() bool { for _, __v := range ${obj} { if fmt.Sprintf("%v", __v) == fmt.Sprintf("%v", ${args[0]}) { return true } }; return false }()`;
|
|
1808
|
+
},
|
|
1809
|
+
concat: (obj, args) => `append(${obj}, ${args.join(', ')}...)`,
|
|
1810
|
+
flat: (obj) => obj,
|
|
1416
1811
|
toString: (obj) => {
|
|
1417
1812
|
importedPackages.add('fmt');
|
|
1418
1813
|
return `fmt.Sprintf("%v", ${obj})`;
|
|
1419
|
-
}
|
|
1814
|
+
},
|
|
1815
|
+
// padStart / padEnd for string arrays (rarely used, but added for completeness)
|
|
1816
|
+
at: (obj, args) => {
|
|
1817
|
+
return `func() interface{} { __i := int(${args[0]}); if __i < 0 { __i = len(${obj}) + __i }; if __i < 0 || __i >= len(${obj}) { return nil }; return ${obj}[__i] }()`;
|
|
1818
|
+
},
|
|
1420
1819
|
};
|
|
1421
1820
|
const mapMethodHandlers = {
|
|
1422
1821
|
set: (obj, args) => `${obj}[${args[0]}] = ${args[1]}`,
|
|
@@ -1549,6 +1948,17 @@ function getTypeArguments(typeArguments) {
|
|
|
1549
1948
|
return `[${args.join(', ')}]`;
|
|
1550
1949
|
}
|
|
1551
1950
|
function getParameterGoType(param) {
|
|
1951
|
+
// Rest parameter: ...args — use variadic syntax
|
|
1952
|
+
if (param.dotDotDotToken) {
|
|
1953
|
+
if (param.type) {
|
|
1954
|
+
let baseType = getType(param.type);
|
|
1955
|
+
// If annotated as T[], the variadic type is T
|
|
1956
|
+
if (baseType.startsWith('[]'))
|
|
1957
|
+
baseType = baseType.slice(2);
|
|
1958
|
+
return `...${baseType}`;
|
|
1959
|
+
}
|
|
1960
|
+
return '...interface{}';
|
|
1961
|
+
}
|
|
1552
1962
|
if (param.type) {
|
|
1553
1963
|
const explicitType = getType(param.type);
|
|
1554
1964
|
return explicitType === ':' ? 'interface{}' : explicitType;
|
|
@@ -1572,9 +1982,7 @@ function getFunctionParametersInfo(parameters) {
|
|
|
1572
1982
|
prefixBlockContent: ''
|
|
1573
1983
|
};
|
|
1574
1984
|
}
|
|
1575
|
-
const hasRequiredAfterDefault = parameters
|
|
1576
|
-
.slice(firstDefaultIndex)
|
|
1577
|
-
.some((p) => !p.initializer);
|
|
1985
|
+
const hasRequiredAfterDefault = parameters.slice(firstDefaultIndex).some((p) => !p.initializer);
|
|
1578
1986
|
if (hasRequiredAfterDefault) {
|
|
1579
1987
|
return {
|
|
1580
1988
|
signature: parameters.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`).join(', '),
|
|
@@ -1714,6 +2122,230 @@ function visitNewSet(node) {
|
|
|
1714
2122
|
const values = initArg.elements.map((el) => `${tmp}[${visit(el)}] = struct{}{}`).join('; ');
|
|
1715
2123
|
return `func() ${setType} { ${tmp} := make(${setType}); ${values}; return ${tmp} }()`;
|
|
1716
2124
|
}
|
|
2125
|
+
function specifierToGoFileName(specifier) {
|
|
2126
|
+
const segments = specifier.split(/[/\\]/);
|
|
2127
|
+
let name = segments[segments.length - 1] || segments[segments.length - 2] || 'import';
|
|
2128
|
+
// Strip all extensions (e.g. .spec.ts → '', .ts → '')
|
|
2129
|
+
name = name.replace(/(\.[^.]+)+$/, '');
|
|
2130
|
+
name = name.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
|
2131
|
+
if (!name)
|
|
2132
|
+
name = 'import';
|
|
2133
|
+
// Deduplicate filename if already taken by a different import
|
|
2134
|
+
let candidate = name + '.go';
|
|
2135
|
+
let i = 2;
|
|
2136
|
+
while (localImportFiles.has(candidate)) {
|
|
2137
|
+
candidate = `${name}_${i}.go`;
|
|
2138
|
+
i++;
|
|
2139
|
+
}
|
|
2140
|
+
return candidate;
|
|
2141
|
+
}
|
|
2142
|
+
function normalizeCjsToEsm(code) {
|
|
2143
|
+
// Remove 'use strict' directive
|
|
2144
|
+
code = code.replace(/['"]use strict['"];?\n?/g, '');
|
|
2145
|
+
// module.exports.X = function(...) { } or exports.X = function(...) { }
|
|
2146
|
+
code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
|
|
2147
|
+
return code;
|
|
2148
|
+
}
|
|
2149
|
+
function includeLocalImport(code, dir, goFileName) {
|
|
2150
|
+
const prevDir = currentFileDir;
|
|
2151
|
+
currentFileDir = dir;
|
|
2152
|
+
// Normalize CommonJS modules to ES module syntax before parsing
|
|
2153
|
+
if (!code.includes('export ') && (code.includes('module.exports') || code.includes('exports.'))) {
|
|
2154
|
+
code = normalizeCjsToEsm(code);
|
|
2155
|
+
}
|
|
2156
|
+
if (!goFileName) {
|
|
2157
|
+
// Inline mode (npm packages): pull declarations into the current transpilation context
|
|
2158
|
+
const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
2159
|
+
for (const stmt of sf.statements) {
|
|
2160
|
+
visit(stmt, { addFunctionOutside: true });
|
|
2161
|
+
}
|
|
2162
|
+
currentFileDir = prevDir;
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
// Separate-file mode (local TS imports): transpile to its own Go file
|
|
2166
|
+
const savedOutsideNodes = outsideNodes;
|
|
2167
|
+
const savedPackages = [...importedPackages];
|
|
2168
|
+
outsideNodes = [];
|
|
2169
|
+
importedPackages.clear();
|
|
2170
|
+
const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
|
|
2171
|
+
const inlineLines = [];
|
|
2172
|
+
for (const stmt of sf.statements) {
|
|
2173
|
+
const result = visit(stmt, { addFunctionOutside: true });
|
|
2174
|
+
if (result.trim())
|
|
2175
|
+
inlineLines.push(result);
|
|
2176
|
+
}
|
|
2177
|
+
const fileImports = [...importedPackages].map((pkg) => `import "${pkg}"`).join('\n');
|
|
2178
|
+
const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
2179
|
+
const fileInline = inlineLines.join('\n');
|
|
2180
|
+
const parts = ['package main'];
|
|
2181
|
+
if (fileImports)
|
|
2182
|
+
parts.push(fileImports);
|
|
2183
|
+
if (fileInline.trim())
|
|
2184
|
+
parts.push(fileInline.trim());
|
|
2185
|
+
if (fileOutside.trim())
|
|
2186
|
+
parts.push(fileOutside.trim());
|
|
2187
|
+
localImportFiles.set(goFileName, parts.join('\n\n'));
|
|
2188
|
+
// Restore main-file state
|
|
2189
|
+
outsideNodes = savedOutsideNodes;
|
|
2190
|
+
importedPackages.clear();
|
|
2191
|
+
for (const p of savedPackages)
|
|
2192
|
+
importedPackages.add(p);
|
|
2193
|
+
currentFileDir = prevDir;
|
|
2194
|
+
}
|
|
2195
|
+
function registerGoPackageAliases(node, goPkg) {
|
|
2196
|
+
const pkgName = goPkg.split('/').pop();
|
|
2197
|
+
if (!node.importClause)
|
|
2198
|
+
return;
|
|
2199
|
+
const clause = node.importClause;
|
|
2200
|
+
// Default import: `import fmt from 'go:fmt'` or `import myFmt from 'go:fmt'`
|
|
2201
|
+
if (clause.name && clause.name.text !== pkgName) {
|
|
2202
|
+
importAliases.set(clause.name.text, pkgName);
|
|
2203
|
+
}
|
|
2204
|
+
if (clause.namedBindings) {
|
|
2205
|
+
if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
2206
|
+
// `import * as myFmt from 'go:fmt'`
|
|
2207
|
+
const localName = clause.namedBindings.name.text;
|
|
2208
|
+
if (localName !== pkgName) {
|
|
2209
|
+
importAliases.set(localName, pkgName);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
else if (ts.isNamedImports(clause.namedBindings)) {
|
|
2213
|
+
// `import { Println, Sprintf as Spf } from 'go:fmt'`
|
|
2214
|
+
for (const el of clause.namedBindings.elements) {
|
|
2215
|
+
if (el.isTypeOnly)
|
|
2216
|
+
continue;
|
|
2217
|
+
const localName = el.name.text;
|
|
2218
|
+
const importedName = el.propertyName?.text ?? localName;
|
|
2219
|
+
importAliases.set(localName, `${pkgName}.${importedName}`);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
function getImportLocalName(node) {
|
|
2225
|
+
const clause = node.importClause;
|
|
2226
|
+
if (!clause)
|
|
2227
|
+
return null;
|
|
2228
|
+
if (clause.name)
|
|
2229
|
+
return clause.name.text;
|
|
2230
|
+
if (clause.namedBindings) {
|
|
2231
|
+
if (ts.isNamespaceImport(clause.namedBindings))
|
|
2232
|
+
return clause.namedBindings.name.text;
|
|
2233
|
+
}
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2236
|
+
// Per-module table: maps Node.js function name → Go expression template.
|
|
2237
|
+
// For default/namespace imports (e.g. `import path from 'node:path'`), entries are registered
|
|
2238
|
+
// as `callHandlers[localName.funcName]`. For named imports (e.g. `import { join } from 'node:path'`),
|
|
2239
|
+
// the Go identifier is stored in importAliases so bare calls like `join(...)` resolve correctly.
|
|
2240
|
+
const nodeModuleMappings = {
|
|
2241
|
+
path: {
|
|
2242
|
+
goPackage: 'path/filepath',
|
|
2243
|
+
functions: {
|
|
2244
|
+
join: (args) => `filepath.Join(${args.join(', ')})`,
|
|
2245
|
+
dirname: (args) => `filepath.Dir(${args[0]})`,
|
|
2246
|
+
basename: (args) => args[1]
|
|
2247
|
+
? `strings.TrimSuffix(filepath.Base(${args[0]}), ${args[1]})`
|
|
2248
|
+
: `filepath.Base(${args[0]})`,
|
|
2249
|
+
extname: (args) => `filepath.Ext(${args[0]})`,
|
|
2250
|
+
resolve: (args) => `func() string { p, _ := filepath.Abs(filepath.Join(${args.join(', ')})); return p }()`
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
// Future node stdlib modules can be added here as additional keys:
|
|
2254
|
+
// fs: { goPackage: 'os', functions: { ... } },
|
|
2255
|
+
// os: { goPackage: 'os', functions: { ... } },
|
|
2256
|
+
};
|
|
2257
|
+
// Mapping from Node.js stdlib module names to Go setup functions.
|
|
2258
|
+
// Each entry adds the required Go imports and registers call handlers for the local identifier.
|
|
2259
|
+
function setupNodeModuleImport(node, nodeModule) {
|
|
2260
|
+
const mapping = nodeModuleMappings[nodeModule];
|
|
2261
|
+
if (!mapping)
|
|
2262
|
+
return;
|
|
2263
|
+
importedPackages.add(mapping.goPackage);
|
|
2264
|
+
const clause = node.importClause;
|
|
2265
|
+
if (!clause)
|
|
2266
|
+
return;
|
|
2267
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
2268
|
+
// Named imports: `import { join, dirname } from 'node:path'`
|
|
2269
|
+
// Map each local name directly to its Go qualified function via importAliases,
|
|
2270
|
+
// so bare calls like `join(...)` resolve to `filepath.Join(...)`.
|
|
2271
|
+
for (const el of clause.namedBindings.elements) {
|
|
2272
|
+
if (el.isTypeOnly)
|
|
2273
|
+
continue;
|
|
2274
|
+
const localName = el.name.text;
|
|
2275
|
+
const importedName = el.propertyName?.text ?? localName;
|
|
2276
|
+
const fn = mapping.functions[importedName];
|
|
2277
|
+
if (fn) {
|
|
2278
|
+
// Register a call handler keyed on the local name
|
|
2279
|
+
callHandlers[localName] = (_caller, args) => fn(args);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
else {
|
|
2284
|
+
// Default import (`import path from 'node:path'`) or
|
|
2285
|
+
// namespace import (`import * as path from 'node:path'`)
|
|
2286
|
+
const localName = getImportLocalName(node) ?? nodeModule;
|
|
2287
|
+
for (const [funcName, fn] of Object.entries(mapping.functions)) {
|
|
2288
|
+
callHandlers[`${localName}.${funcName}`] = (_caller, args) => fn(args);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
function visitImportDeclaration(node) {
|
|
2293
|
+
if (!ts.isStringLiteral(node.moduleSpecifier))
|
|
2294
|
+
return '';
|
|
2295
|
+
const moduleSpec = node.moduleSpecifier.text;
|
|
2296
|
+
// Relative imports → transpile to a separate Go file
|
|
2297
|
+
if (moduleSpec.startsWith('.') || moduleSpec.startsWith('/')) {
|
|
2298
|
+
// Key combines current dir + specifier to avoid cross-package collisions
|
|
2299
|
+
const key = `${currentFileDir ?? ''}::${moduleSpec}`;
|
|
2300
|
+
if (fileResolver && !includedLocalImports.has(key)) {
|
|
2301
|
+
includedLocalImports.add(key);
|
|
2302
|
+
const resolved = fileResolver(moduleSpec, currentFileDir);
|
|
2303
|
+
if (resolved) {
|
|
2304
|
+
const goFileName = specifierToGoFileName(moduleSpec);
|
|
2305
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
return '';
|
|
2309
|
+
}
|
|
2310
|
+
// Type-only imports contribute nothing to runtime
|
|
2311
|
+
if (node.importClause?.isTypeOnly)
|
|
2312
|
+
return '';
|
|
2313
|
+
// Go standard library: `import { Println } from 'go:fmt'` → import "fmt"
|
|
2314
|
+
if (moduleSpec.startsWith('go:')) {
|
|
2315
|
+
const goPkg = moduleSpec.slice(3);
|
|
2316
|
+
importedPackages.add(goPkg);
|
|
2317
|
+
registerGoPackageAliases(node, goPkg);
|
|
2318
|
+
return '';
|
|
2319
|
+
}
|
|
2320
|
+
// Node.js standard library: `import path from 'node:path'` → mapped Go packages
|
|
2321
|
+
if (moduleSpec.startsWith('node:')) {
|
|
2322
|
+
const nodeModule = moduleSpec.slice(5);
|
|
2323
|
+
setupNodeModuleImport(node, nodeModule);
|
|
2324
|
+
return '';
|
|
2325
|
+
}
|
|
2326
|
+
// npm package (bare specifier) → transpile to its own Go file
|
|
2327
|
+
const npmKey = `npm::${moduleSpec}`;
|
|
2328
|
+
if (fileResolver && !includedLocalImports.has(npmKey)) {
|
|
2329
|
+
includedLocalImports.add(npmKey);
|
|
2330
|
+
const resolved = fileResolver(moduleSpec, currentFileDir);
|
|
2331
|
+
if (resolved) {
|
|
2332
|
+
const goFileName = specifierToGoFileName(moduleSpec);
|
|
2333
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
// Register default/namespace import name as a "stripped" namespace
|
|
2337
|
+
// e.g. `import ts from 'typescript'` → ts.createSourceFile → createSourceFile
|
|
2338
|
+
if (node.importClause) {
|
|
2339
|
+
const clause = node.importClause;
|
|
2340
|
+
if (clause.name) {
|
|
2341
|
+
defaultImportNamespaces.add(clause.name.text);
|
|
2342
|
+
}
|
|
2343
|
+
else if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
|
|
2344
|
+
defaultImportNamespaces.add(clause.namedBindings.name.text);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return '';
|
|
2348
|
+
}
|
|
1717
2349
|
function getForOfVarNames(initializer) {
|
|
1718
2350
|
if (!ts.isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
|
|
1719
2351
|
return ['_'];
|