typenative 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/transpiler.js CHANGED
@@ -1,12 +1,269 @@
1
- import ts from 'typescript';
2
- import { customAlphabet } from 'nanoid';
3
- const goSafeId = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 8);
4
- let TypeCheker;
1
+ // Shim over the removed `typescript` import: predicates and kind names operate
2
+ // on the normalized JSON AST ({kind: 'Xxx', ...} plain objects) produced by
3
+ // src/parse-node.ts (Node) or the tsparser Go tool (self-hosted).
4
+ // Kept as a `ts` object so the transpiler code keeps its shape.
5
+ function isArrayBindingPattern(n) {
6
+ return n?.kind === 'ArrayBindingPattern';
7
+ }
8
+ function isArrayLiteralExpression(n) {
9
+ return n?.kind === 'ArrayLiteralExpression';
10
+ }
11
+ function isArrayTypeNode(n) {
12
+ return n?.kind === 'ArrayTypeNode';
13
+ }
14
+ function isArrowFunction(n) {
15
+ return n?.kind === 'ArrowFunction';
16
+ }
17
+ function isAsExpression(n) {
18
+ return n?.kind === 'AsExpression';
19
+ }
20
+ function isAwaitExpression(n) {
21
+ return n?.kind === 'AwaitExpression';
22
+ }
23
+ function isBinaryExpression(n) {
24
+ return n?.kind === 'BinaryExpression';
25
+ }
26
+ function isBlock(n) {
27
+ return n?.kind === 'Block';
28
+ }
29
+ function isBreakStatement(n) {
30
+ return n?.kind === 'BreakStatement';
31
+ }
32
+ function isCallExpression(n) {
33
+ return n?.kind === 'CallExpression';
34
+ }
35
+ function isCaseBlock(n) {
36
+ return n?.kind === 'CaseBlock';
37
+ }
38
+ function isCaseClause(n) {
39
+ return n?.kind === 'CaseClause';
40
+ }
41
+ function isClassDeclaration(n) {
42
+ return n?.kind === 'ClassDeclaration';
43
+ }
44
+ function isConditionalExpression(n) {
45
+ return n?.kind === 'ConditionalExpression';
46
+ }
47
+ function isConstructorDeclaration(n) {
48
+ return n?.kind === 'ConstructorDeclaration';
49
+ }
50
+ function isDefaultClause(n) {
51
+ return n?.kind === 'DefaultClause';
52
+ }
53
+ function isDoStatement(n) {
54
+ return n?.kind === 'DoStatement';
55
+ }
56
+ function isElementAccessExpression(n) {
57
+ return n?.kind === 'ElementAccessExpression';
58
+ }
59
+ function isEnumDeclaration(n) {
60
+ return n?.kind === 'EnumDeclaration';
61
+ }
62
+ function isExportAssignment(n) {
63
+ return n?.kind === 'ExportAssignment';
64
+ }
65
+ function isExportDeclaration(n) {
66
+ return n?.kind === 'ExportDeclaration';
67
+ }
68
+ function isExpressionStatement(n) {
69
+ return n?.kind === 'ExpressionStatement';
70
+ }
71
+ function isForInStatement(n) {
72
+ return n?.kind === 'ForInStatement';
73
+ }
74
+ function isForOfStatement(n) {
75
+ return n?.kind === 'ForOfStatement';
76
+ }
77
+ function isForStatement(n) {
78
+ return n?.kind === 'ForStatement';
79
+ }
80
+ function isFunctionDeclaration(n) {
81
+ return n?.kind === 'FunctionDeclaration';
82
+ }
83
+ function isFunctionExpression(n) {
84
+ return n?.kind === 'FunctionExpression';
85
+ }
86
+ function isFunctionTypeNode(n) {
87
+ return n?.kind === 'FunctionTypeNode';
88
+ }
89
+ function isGetAccessor(n) {
90
+ return n?.kind === 'GetAccessor';
91
+ }
92
+ function isIdentifier(n) {
93
+ return n?.kind === 'Identifier';
94
+ }
95
+ function isIfStatement(n) {
96
+ return n?.kind === 'IfStatement';
97
+ }
98
+ function isImportDeclaration(n) {
99
+ return n?.kind === 'ImportDeclaration';
100
+ }
101
+ function isInterfaceDeclaration(n) {
102
+ return n?.kind === 'InterfaceDeclaration';
103
+ }
104
+ function isLiteralTypeNode(n) {
105
+ return n?.kind === 'LiteralTypeNode';
106
+ }
107
+ function isMethodDeclaration(n) {
108
+ return n?.kind === 'MethodDeclaration';
109
+ }
110
+ function isMethodSignature(n) {
111
+ return n?.kind === 'MethodSignature';
112
+ }
113
+ function isNamedImports(n) {
114
+ return n?.kind === 'NamedImports';
115
+ }
116
+ function isNamespaceImport(n) {
117
+ return n?.kind === 'NamespaceImport';
118
+ }
119
+ function isNewExpression(n) {
120
+ return n?.kind === 'NewExpression';
121
+ }
122
+ function isNoSubstitutionTemplateLiteral(n) {
123
+ return n?.kind === 'NoSubstitutionTemplateLiteral';
124
+ }
125
+ function isNonNullExpression(n) {
126
+ return n?.kind === 'NonNullExpression';
127
+ }
128
+ function isNumericLiteral(n) {
129
+ return n?.kind === 'NumericLiteral';
130
+ }
131
+ function isObjectBindingPattern(n) {
132
+ return n?.kind === 'ObjectBindingPattern';
133
+ }
134
+ function isObjectLiteralExpression(n) {
135
+ return n?.kind === 'ObjectLiteralExpression';
136
+ }
137
+ function isOmittedExpression(n) {
138
+ return n?.kind === 'OmittedExpression';
139
+ }
140
+ function isParenthesizedExpression(n) {
141
+ return n?.kind === 'ParenthesizedExpression';
142
+ }
143
+ function isPostfixUnaryExpression(n) {
144
+ return n?.kind === 'PostfixUnaryExpression';
145
+ }
146
+ function isPrefixUnaryExpression(n) {
147
+ return n?.kind === 'PrefixUnaryExpression';
148
+ }
149
+ function isPropertyAccessExpression(n) {
150
+ return n?.kind === 'PropertyAccessExpression';
151
+ }
152
+ function isPropertyAssignment(n) {
153
+ return n?.kind === 'PropertyAssignment';
154
+ }
155
+ function isPropertyDeclaration(n) {
156
+ return n?.kind === 'PropertyDeclaration';
157
+ }
158
+ function isPropertySignature(n) {
159
+ return n?.kind === 'PropertySignature';
160
+ }
161
+ function isRegularExpressionLiteral(n) {
162
+ return n?.kind === 'RegularExpressionLiteral';
163
+ }
164
+ function isReturnStatement(n) {
165
+ return n?.kind === 'ReturnStatement';
166
+ }
167
+ function isSetAccessor(n) {
168
+ return n?.kind === 'SetAccessor';
169
+ }
170
+ function isShorthandPropertyAssignment(n) {
171
+ return n?.kind === 'ShorthandPropertyAssignment';
172
+ }
173
+ function isSourceFile(n) {
174
+ return n?.kind === 'SourceFile';
175
+ }
176
+ function isSpreadElement(n) {
177
+ return n?.kind === 'SpreadElement';
178
+ }
179
+ function isStringLiteral(n) {
180
+ return n?.kind === 'StringLiteral';
181
+ }
182
+ function isSwitchStatement(n) {
183
+ return n?.kind === 'SwitchStatement';
184
+ }
185
+ function isTemplateExpression(n) {
186
+ return n?.kind === 'TemplateExpression';
187
+ }
188
+ function isThrowStatement(n) {
189
+ return n?.kind === 'ThrowStatement';
190
+ }
191
+ function isTryStatement(n) {
192
+ return n?.kind === 'TryStatement';
193
+ }
194
+ function isTypeAliasDeclaration(n) {
195
+ return n?.kind === 'TypeAliasDeclaration';
196
+ }
197
+ function isTypeAssertionExpression(n) {
198
+ return n?.kind === 'TypeAssertionExpression';
199
+ }
200
+ function isTypeReferenceNode(n) {
201
+ return n?.kind === 'TypeReferenceNode';
202
+ }
203
+ function isUnionTypeNode(n) {
204
+ return n?.kind === 'UnionTypeNode';
205
+ }
206
+ function isVariableDeclaration(n) {
207
+ return n?.kind === 'VariableDeclaration';
208
+ }
209
+ function isVariableDeclarationList(n) {
210
+ return n?.kind === 'VariableDeclarationList';
211
+ }
212
+ function isVariableStatement(n) {
213
+ return n?.kind === 'VariableStatement';
214
+ }
215
+ function isWhileStatement(n) {
216
+ return n?.kind === 'WhileStatement';
217
+ }
218
+ function isToken(_n) {
219
+ return true;
220
+ }
221
+ const SyntaxKind = {
222
+ BooleanKeyword: 'BooleanKeyword',
223
+ ExclamationToken: 'ExclamationToken',
224
+ ExtendsKeyword: 'ExtendsKeyword',
225
+ FalseKeyword: 'FalseKeyword',
226
+ MinusMinusToken: 'MinusMinusToken',
227
+ MinusToken: 'MinusToken',
228
+ NullKeyword: 'NullKeyword',
229
+ NumberKeyword: 'NumberKeyword',
230
+ PlusPlusToken: 'PlusPlusToken',
231
+ PlusToken: 'PlusToken',
232
+ QuestionDotToken: 'QuestionDotToken',
233
+ QuestionQuestionToken: 'QuestionQuestionToken',
234
+ StaticKeyword: 'StaticKeyword',
235
+ StringKeyword: 'StringKeyword',
236
+ SuperKeyword: 'SuperKeyword',
237
+ ThisKeyword: 'ThisKeyword',
238
+ TildeToken: 'TildeToken',
239
+ TrueKeyword: 'TrueKeyword',
240
+ UndefinedKeyword: 'UndefinedKeyword',
241
+ };
242
+ // Local replacement for nanoid's customAlphabet — must stay transpilable by
243
+ // TypeNative itself (no dependencies), only using mapped String methods.
244
+ const GO_SAFE_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
245
+ function goSafeId() {
246
+ let id = '';
247
+ for (let i = 0; i < 8; i++) {
248
+ id += GO_SAFE_ALPHABET.charAt(Math.floor(Math.random() * GO_SAFE_ALPHABET.length));
249
+ }
250
+ return id;
251
+ }
252
+ let parseFunction;
5
253
  const importedPackages = new Set();
6
254
  let outsideNodes = [];
7
255
  const classNames = new Set();
8
256
  let promiseResolveName = '';
9
- const dangerousNames = new Set(['main']);
257
+ // Go keywords that cannot be used as identifiers
258
+ const dangerousNames = new Set([
259
+ 'main',
260
+ // Go reserved keywords
261
+ 'break', 'case', 'chan', 'const', 'continue',
262
+ 'default', 'defer', 'else', 'fallthrough', 'for',
263
+ 'func', 'go', 'goto', 'if', 'import',
264
+ 'interface', 'map', 'package', 'range', 'return',
265
+ 'select', 'struct', 'switch', 'type', 'var',
266
+ ]);
10
267
  const renamedFunctions = new Map();
11
268
  const variableTypes = new Map();
12
269
  const variableGoTypes = new Map();
@@ -19,6 +276,10 @@ const enumNames = new Set();
19
276
  const enumBaseTypes = new Map();
20
277
  // Maps local TS name → Go qualified name (e.g. 'Println' → 'fmt.Println', 'myFmt' → 'fmt')
21
278
  const importAliases = new Map();
279
+ // Tracks static methods per class: Set of "ClassName.methodName" strings
280
+ const classStaticMethods = new Set();
281
+ // Tracks static properties per class: Set of "ClassName.propName" strings
282
+ const classStaticProps = new Set();
22
283
  // Callback for resolving import specifiers to source code.
23
284
  // specifier: the raw import string (relative path or package name)
24
285
  // fromDir: directory of the file containing the import (null = main file's dir)
@@ -30,11 +291,108 @@ let currentFileDir = null;
30
291
  const includedLocalImports = new Set();
31
292
  // Collects Go source files generated from local TS imports (filename → content)
32
293
  let localImportFiles = new Map();
294
+ // Token kind names → operator source text (JSON AST tokens carry kind names)
295
+ const OPERATOR_TEXT = {
296
+ EqualsEqualsEqualsToken: '===',
297
+ ExclamationEqualsEqualsToken: '!==',
298
+ EqualsEqualsToken: '==',
299
+ ExclamationEqualsToken: '!=',
300
+ LessThanToken: '<',
301
+ LessThanEqualsToken: '<=',
302
+ GreaterThanToken: '>',
303
+ GreaterThanEqualsToken: '>=',
304
+ PlusToken: '+',
305
+ MinusToken: '-',
306
+ AsteriskToken: '*',
307
+ SlashToken: '/',
308
+ PercentToken: '%',
309
+ AsteriskAsteriskToken: '**',
310
+ AmpersandToken: '&',
311
+ BarToken: '|',
312
+ CaretToken: '^',
313
+ LessThanLessThanToken: '<<',
314
+ GreaterThanGreaterThanToken: '>>',
315
+ AmpersandAmpersandToken: '&&',
316
+ BarBarToken: '||',
317
+ EqualsToken: '=',
318
+ PlusEqualsToken: '+=',
319
+ MinusEqualsToken: '-=',
320
+ AsteriskEqualsToken: '*=',
321
+ SlashEqualsToken: '/=',
322
+ PercentEqualsToken: '%=',
323
+ CommaToken: ',',
324
+ QuestionQuestionToken: '??'
325
+ };
326
+ function operatorTokenText(token) {
327
+ return OPERATOR_TEXT[token?.kind] ?? token?.kind ?? '';
328
+ }
329
+ function defaultParseFunction(_code) {
330
+ throw new Error('transpileToNative: no parse function injected (options.parse)');
331
+ }
332
+ // Renders a type node's source text syntactically (no typechecker needed).
333
+ function typeNodeToText(node) {
334
+ if (!node)
335
+ return 'any';
336
+ const keywordMap = {
337
+ NumberKeyword: 'number',
338
+ StringKeyword: 'string',
339
+ BooleanKeyword: 'boolean',
340
+ AnyKeyword: 'any',
341
+ UnknownKeyword: 'any',
342
+ VoidKeyword: 'void',
343
+ UndefinedKeyword: 'undefined',
344
+ NullKeyword: 'null',
345
+ NeverKeyword: 'never',
346
+ ObjectKeyword: 'object',
347
+ TrueKeyword: 'true',
348
+ FalseKeyword: 'false'
349
+ };
350
+ if (keywordMap[node.kind])
351
+ return keywordMap[node.kind];
352
+ if (node.kind === 'TypeReferenceNode' && node.typeName) {
353
+ return typeNodeToText(node.typeName);
354
+ }
355
+ if (node.kind === 'Identifier')
356
+ return node.text;
357
+ if (node.kind === 'ArrayTypeNode' && node.elementType) {
358
+ return `${'{'}${''}}typeNodeToText(node.elementType)[]`;
359
+ }
360
+ return 'any';
361
+ }
362
+ // Iterates the child nodes of a normalized JSON AST node (own enumerable
363
+ function childNodes(node) {
364
+ const out = [];
365
+ for (const key of Object.keys(node ?? {})) {
366
+ if (key === 'kind' || key === 'text')
367
+ continue;
368
+ const v = node[key];
369
+ if (v && typeof v === 'object' && typeof v.kind === 'string') {
370
+ out.push(v);
371
+ }
372
+ else if (Array.isArray(v)) {
373
+ for (const el of v) {
374
+ if (el && typeof el === 'object' && typeof el.kind === 'string')
375
+ out.push(el);
376
+ }
377
+ }
378
+ }
379
+ return out;
380
+ }
381
+ // Default import namespaces from npm/local packages (e.g. `import ts from 'typescript'` → 'ts')
382
+ // Property accesses on these are stripped: ts.createSourceFile → createSourceFile
383
+ const defaultImportNamespaces = new Set();
384
+ // Go helper functions required by the current transpilation (e.g. 'exec', 'readFile').
385
+ // Helper sources are appended to every generated Go file that references them.
386
+ const usedHelpers = new Set();
387
+ // Packages that were imported only because a helper needs them. Tracked so
388
+ // per-file import lists can exclude them (helpers are emitted once, in the
389
+ // main file, which carries these imports instead).
390
+ const helperProvidedPackages = new Set();
33
391
  export function transpileToNative(code, options) {
34
392
  fileResolver = options?.readFile ?? null;
393
+ parseFunction = options?.parse ?? defaultParseFunction;
35
394
  currentFileDir = null;
36
- const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
37
- TypeCheker = ts.createProgram(['main.ts'], {}).getTypeChecker();
395
+ const sourceFile = parseFunction(code);
38
396
  importedPackages.clear();
39
397
  outsideNodes = [];
40
398
  classNames.clear();
@@ -52,6 +410,11 @@ export function transpileToNative(code, options) {
52
410
  importAliases.clear();
53
411
  includedLocalImports.clear();
54
412
  localImportFiles = new Map();
413
+ defaultImportNamespaces.clear();
414
+ classStaticMethods.clear();
415
+ classStaticProps.clear();
416
+ usedHelpers.clear();
417
+ helperProvidedPackages.clear();
55
418
  const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
56
419
  const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
57
420
  const main = `package main
@@ -62,18 +425,19 @@ func main() {
62
425
  ${transpiledCode.trim()}
63
426
  }
64
427
 
65
- ${transpiledCodeOutside.trim()}`;
428
+ ${transpiledCodeOutside.trim()}
429
+ ${emitGoHelpers()}`.trimEnd();
66
430
  return { main, files: localImportFiles };
67
431
  }
68
432
  export function visit(node, options = {}) {
69
433
  let code = '';
70
- if (ts.isSourceFile(node)) {
71
- return node.statements
434
+ if (isSourceFile(node)) {
435
+ return (node.statements ?? [])
72
436
  .map((n) => visit(n, { addFunctionOutside: true }))
73
437
  .filter((n) => !!n)
74
438
  .join(options.inline ? '' : '\n\t');
75
439
  }
76
- else if (ts.isIdentifier(node)) {
440
+ else if (isIdentifier(node)) {
77
441
  if (node.text === 'undefined')
78
442
  return 'nil';
79
443
  const goAlias = importAliases.get(node.text);
@@ -81,31 +445,31 @@ export function visit(node, options = {}) {
81
445
  return goAlias;
82
446
  return getSafeName(node.text);
83
447
  }
84
- else if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
448
+ else if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
85
449
  return toGoStringLiteral(node.text);
86
450
  }
87
- else if (ts.isAsExpression(node)) {
451
+ else if (isAsExpression(node)) {
88
452
  return visit(node.expression);
89
453
  }
90
- else if (ts.isTypeAssertionExpression(node)) {
454
+ else if (isTypeAssertionExpression(node)) {
91
455
  return visit(node.expression);
92
456
  }
93
- else if (ts.isTemplateExpression(node)) {
457
+ else if (isTemplateExpression(node)) {
94
458
  return visitTemplateExpression(node);
95
459
  }
96
- else if (ts.isNumericLiteral(node)) {
460
+ else if (isNumericLiteral(node)) {
97
461
  return `float64(${node.text})`;
98
462
  }
99
- else if (ts.isToken(node) && node.kind === ts.SyntaxKind.TrueKeyword) {
463
+ else if (isToken(node) && node.kind === 'TrueKeyword') {
100
464
  return `true`;
101
465
  }
102
- else if (ts.isToken(node) && node.kind === ts.SyntaxKind.FalseKeyword) {
466
+ else if (isToken(node) && node.kind === 'FalseKeyword') {
103
467
  return `false`;
104
468
  }
105
- else if (ts.isToken(node) && node.kind === ts.SyntaxKind.NullKeyword) {
469
+ else if (isToken(node) && node.kind === 'NullKeyword') {
106
470
  return `nil`;
107
471
  }
108
- else if (ts.isRegularExpressionLiteral(node)) {
472
+ else if (isRegularExpressionLiteral(node)) {
109
473
  importedPackages.add('regexp');
110
474
  const text = node.text; // e.g. /pattern/flags
111
475
  const lastSlash = text.lastIndexOf('/');
@@ -115,37 +479,86 @@ export function visit(node, options = {}) {
115
479
  const escaped = pattern.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
116
480
  return `regexp.MustCompile("${goFlags}${escaped}")`;
117
481
  }
118
- else if (ts.isArrayLiteralExpression(node)) {
119
- const type = ts.isVariableDeclaration(node.parent) ? getType(node.parent.type, true) : '';
120
- return `[]${type} {${node.elements.map((e) => visit(e)).join(', ')}}`;
482
+ else if (isArrayLiteralExpression(node)) {
483
+ const type = isVariableDeclaration(node.parent) ? getType(node.parent.type, true) : '';
484
+ const hasSpread = (node.elements ?? []).some((e) => isSpreadElement(e));
485
+ if (hasSpread) {
486
+ return visitSpreadArrayLiteral(node, type);
487
+ }
488
+ return `[]${type} {${(node.elements ?? []).map((e) => visit(e)).join(', ')}}`;
121
489
  }
122
- else if (ts.isBlock(node)) {
123
- return `{\n\t\t${options.prefixBlockContent ?? ''}${node.statements
490
+ else if (isBlock(node)) {
491
+ return `{\n\t\t${options.prefixBlockContent ?? ''}${(node.statements ?? [])
124
492
  .map((n) => visit(n))
125
493
  .join('\t')}${options.extraBlockContent ?? ''}}${options.inline ? '' : '\n\t'}`;
126
494
  }
127
- else if (ts.isElementAccessExpression(node)) {
495
+ else if (isElementAccessExpression(node)) {
128
496
  if (hasQuestionDot(node)) {
129
497
  return visitOptionalElementAccess(node);
130
498
  }
131
499
  return `${visit(node.expression)}[int(${visit(node.argumentExpression)})]`;
132
500
  }
133
- else if (ts.isPropertyAccessExpression(node)) {
501
+ else if (isPropertyAccessExpression(node)) {
134
502
  if (hasQuestionDot(node)) {
135
503
  return visitOptionalPropertyAccess(node);
136
504
  }
137
- if (ts.isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
505
+ if (isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
138
506
  return `${getSafeName(node.expression.text)}_${getEnumMemberName(node.name)}`;
139
507
  }
508
+ // Strip default import namespace: `ts.createSourceFile` → `createSourceFile`
509
+ if (isIdentifier(node.expression) && defaultImportNamespaces.has(node.expression.text)) {
510
+ return visit(node.name);
511
+ }
512
+ // Static member access: `Counter.count` → `Counter_count`
513
+ if (isIdentifier(node.expression)) {
514
+ const key = `${node.expression.text}.${node.name.text}`;
515
+ if (classStaticMethods.has(key) || classStaticProps.has(key)) {
516
+ return `${node.expression.text}_${node.name.text}`;
517
+ }
518
+ }
140
519
  const leftSide = visit(node.expression);
141
520
  const rightSide = visit(node.name);
142
521
  const objectType = resolveExpressionType(node.expression);
143
522
  return getAcessString(leftSide, rightSide, objectType);
144
523
  }
145
- else if (ts.isVariableDeclaration(node)) {
524
+ else if (isVariableDeclaration(node)) {
525
+ // Object destructuring: const { x, y } = obj
526
+ if (isObjectBindingPattern(node.name) && node.initializer) {
527
+ const initExpr = visit(node.initializer);
528
+ const parts = node.name.elements.map((el) => {
529
+ const localName = visit(el.name);
530
+ const propName = el.propertyName ? visit(el.propertyName) : localName;
531
+ const defaultVal = el.initializer ? visit(el.initializer) : undefined;
532
+ if (defaultVal) {
533
+ return `${localName} := func() interface{} { if ${initExpr}.${propName} == nil { return ${defaultVal} }; return ${initExpr}.${propName} }()`;
534
+ }
535
+ return `${localName} := ${initExpr}.${propName}`;
536
+ });
537
+ return parts.join(';\n\t');
538
+ }
539
+ // Array destructuring: const [a, b] = arr
540
+ if (isArrayBindingPattern(node.name) && node.initializer) {
541
+ const initExpr = visit(node.initializer);
542
+ const tmpVar = getTempName('arr');
543
+ const parts = [`${tmpVar} := ${initExpr}`];
544
+ node.name.elements.forEach((el, idx) => {
545
+ if (isOmittedExpression(el))
546
+ return;
547
+ const bindEl = el;
548
+ const localName = visit(bindEl.name);
549
+ // Variables starting with _ are intentionally unused — use blank identifier
550
+ if (localName === '_' || localName.startsWith('_')) {
551
+ parts.push(`_ = ${tmpVar}[${idx}]`);
552
+ }
553
+ else {
554
+ parts.push(`${localName} := ${tmpVar}[${idx}]`);
555
+ }
556
+ });
557
+ return parts.join(';\n\t');
558
+ }
146
559
  const type = getType(node.type);
147
560
  // Track variable type for type-aware method dispatch
148
- if (ts.isIdentifier(node.name)) {
561
+ if (isIdentifier(node.name)) {
149
562
  if (node.type) {
150
563
  variableGoTypes.set(node.name.text, getType(node.type));
151
564
  }
@@ -160,8 +573,8 @@ export function visit(node, options = {}) {
160
573
  }
161
574
  }
162
575
  else if (node.initializer &&
163
- ts.isNewExpression(node.initializer) &&
164
- ts.isIdentifier(node.initializer.expression)) {
576
+ isNewExpression(node.initializer) &&
577
+ isIdentifier(node.initializer.expression)) {
165
578
  if (node.initializer.expression.text === 'RegExp') {
166
579
  variableTypes.set(node.name.text, 'RegExp');
167
580
  }
@@ -170,7 +583,7 @@ export function visit(node, options = {}) {
170
583
  variableClassNames.set(node.name.text, node.initializer.expression.text);
171
584
  }
172
585
  }
173
- else if (node.initializer && ts.isRegularExpressionLiteral(node.initializer)) {
586
+ else if (node.initializer && isRegularExpressionLiteral(node.initializer)) {
174
587
  variableTypes.set(node.name.text, 'RegExp');
175
588
  }
176
589
  if (!variableGoTypes.has(node.name.text) && node.initializer) {
@@ -192,17 +605,30 @@ export function visit(node, options = {}) {
192
605
  }
193
606
  return `${type === ':' ? '' : 'var '}${visit(node.name)} ${type}${type === ':' ? '' : ' '}${initializer}`;
194
607
  }
195
- else if (ts.isCallExpression(node)) {
608
+ else if (isCallExpression(node)) {
196
609
  if (hasQuestionDot(node) ||
197
- (ts.isPropertyAccessExpression(node.expression) && hasQuestionDot(node.expression))) {
610
+ (isPropertyAccessExpression(node.expression) && hasQuestionDot(node.expression))) {
198
611
  return visitOptionalCall(node);
199
612
  }
613
+ // IIFE with named function expression: (function name() { ... })()
614
+ if (isParenthesizedExpression(node.expression) &&
615
+ isFunctionExpression(node.expression.expression)) {
616
+ const fn = node.expression.expression;
617
+ const parameterInfo = getFunctionParametersInfo(fn.parameters ?? []);
618
+ if (fn.body && isBlock(fn.body)) {
619
+ prescanVariableDeclarations(fn.body);
620
+ }
621
+ const inferredRetType = inferFunctionBodyReturnType(fn);
622
+ const returnType = inferredRetType ? ` ${inferredRetType}` : '';
623
+ const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
624
+ return `func(${parameterInfo.signature})${returnType} ${visit(fn.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}(${args})`;
625
+ }
200
626
  // Handle setTimeout specially to get raw delay value
201
- if (ts.isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
627
+ if (isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
202
628
  importedPackages.add('time');
203
- const callback = visit(node.arguments[0]);
204
- const delayNode = node.arguments[1];
205
- const delay = ts.isNumericLiteral(delayNode) ? delayNode.text : visit(delayNode);
629
+ const callback = visit((node.arguments ?? [])[0]);
630
+ const delayNode = (node.arguments ?? [])[1];
631
+ const delay = isNumericLiteral(delayNode) ? delayNode.text : visit(delayNode);
206
632
  return `time.AfterFunc(${delay} * time.Millisecond, ${callback.trimEnd()})`;
207
633
  }
208
634
  const arrayHigherOrderCall = visitArrayHigherOrderCall(node);
@@ -211,56 +637,86 @@ export function visit(node, options = {}) {
211
637
  }
212
638
  const caller = visit(node.expression);
213
639
  const safeCaller = getSafeName(caller);
214
- const typeArgs = getTypeArguments(node.typeArguments);
215
- const args = node.arguments.map((a) => visit(a));
640
+ const typeArgs = getTypeArguments((node.typeArguments ?? []));
641
+ // Handle spread arguments: fn(...arr) → fn(arr...)
642
+ const hasSpreadArg = (node.arguments ?? []).some((a) => isSpreadElement(a));
643
+ const args = hasSpreadArg
644
+ ? (node.arguments ?? []).map((a) => isSpreadElement(a) ? `${visit(a.expression)}...` : visit(a))
645
+ : (node.arguments ?? []).map((a) => visit(a));
216
646
  // Resolve object type for type-aware method dispatch
217
647
  let objectType;
218
- if (ts.isPropertyAccessExpression(node.expression)) {
648
+ if (isPropertyAccessExpression(node.expression)) {
219
649
  objectType = resolveExpressionType(node.expression.expression);
220
650
  }
221
651
  return getCallString(safeCaller, args, typeArgs, objectType);
222
652
  }
223
- else if (ts.isPrefixUnaryExpression(node)) {
653
+ else if (isPrefixUnaryExpression(node)) {
224
654
  return `${getOperatorText(node.operator)}${visit(node.operand)}`;
225
655
  }
226
- else if (ts.isPostfixUnaryExpression(node)) {
656
+ else if (isPostfixUnaryExpression(node)) {
227
657
  return `${visit(node.operand, { inline: true })}${getOperatorText(node.operator)}`;
228
658
  }
229
- else if (ts.isConditionalExpression(node)) {
659
+ else if (isConditionalExpression(node)) {
230
660
  return visitConditionalExpression(node);
231
661
  }
232
- else if (ts.isBinaryExpression(node)) {
233
- if (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
662
+ else if (isBinaryExpression(node)) {
663
+ if (node.operatorToken.kind === 'QuestionQuestionToken') {
234
664
  return visitNullishCoalescingExpression(node);
235
665
  }
236
- let op = node.operatorToken.getText();
666
+ let op = operatorTokenText(node.operatorToken);
237
667
  if (op === '===')
238
668
  op = '==';
239
669
  if (op === '!==')
240
670
  op = '!=';
671
+ // Go's % is not defined on float64 (TS numbers all map to float64)
672
+ if (op === '%') {
673
+ importedPackages.add('math');
674
+ return `math.Mod(${visit(node.left)}, ${visit(node.right)})`;
675
+ }
676
+ if (op === '%=') {
677
+ importedPackages.add('math');
678
+ const left = visit(node.left);
679
+ return `${left} = math.Mod(${left}, ${visit(node.right)})`;
680
+ }
241
681
  return `${visit(node.left)} ${op} ${visit(node.right)}`;
242
682
  }
243
- else if (ts.isParenthesizedExpression(node)) {
683
+ else if (isParenthesizedExpression(node)) {
244
684
  return `(${visit(node.expression)})`;
245
685
  }
246
- else if (ts.isAwaitExpression(node)) {
686
+ else if (isAwaitExpression(node)) {
247
687
  return `<-${visit(node.expression)}`;
248
688
  }
249
- else if (ts.isVariableDeclarationList(node)) {
250
- return (node.declarations.map((n) => visit(n)).join(options.inline ? ';' : ';\n\t') +
689
+ else if (isVariableDeclarationList(node)) {
690
+ return ((node.declarations ?? []).map((n) => visit(n)).join(options.inline ? ';' : ';\n\t') +
251
691
  (options.inline ? '' : ';\n\t'));
252
692
  }
253
- else if (ts.isExpressionStatement(node)) {
693
+ else if (isExpressionStatement(node)) {
254
694
  return visit(node.expression) + (options.inline ? '' : ';\n\t');
255
695
  }
256
- else if (ts.isForStatement(node)) {
696
+ else if (isForStatement(node)) {
257
697
  return `for ${visit(node.initializer, { inline: true })}; ${visit(node.condition, {
258
698
  inline: true
259
699
  })}; ${visit(node.incrementor, { inline: true })}${visit(node.statement)}`;
260
700
  }
261
- else if (ts.isForOfStatement(node)) {
262
- const iterExpr = visit(node.expression, { inline: true });
263
- const iterType = inferExpressionType(node.expression);
701
+ else if (isForInStatement(node)) {
702
+ const varName = isVariableDeclarationList(node.initializer)
703
+ ? visit(node.initializer.declarations[0].name)
704
+ : visit(node.initializer);
705
+ return `for ${varName} := range ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
706
+ }
707
+ else if (isForOfStatement(node)) {
708
+ // Unwrap Object.entries(x) → treat x as the iterable
709
+ let iterNode = node.expression;
710
+ if (isCallExpression(iterNode) &&
711
+ isPropertyAccessExpression(iterNode.expression) &&
712
+ isIdentifier(iterNode.expression.expression) &&
713
+ iterNode.expression.expression.text === 'Object' &&
714
+ iterNode.expression.name.text === 'entries' &&
715
+ iterNode.arguments.length > 0) {
716
+ iterNode = iterNode.arguments[0];
717
+ }
718
+ const iterExpr = visit(iterNode, { inline: true });
719
+ const iterType = inferExpressionType(iterNode);
264
720
  if (iterType && iterType.startsWith('map[')) {
265
721
  const valueType = extractMapValueType(iterType);
266
722
  const isSet = valueType === 'struct{}';
@@ -277,50 +733,62 @@ export function visit(node, options = {}) {
277
733
  }
278
734
  return `for _,${visit(node.initializer, { inline: true })}= range ${iterExpr}${visit(node.statement)}`;
279
735
  }
280
- else if (ts.isWhileStatement(node)) {
736
+ else if (isWhileStatement(node)) {
281
737
  return `for ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
282
738
  }
283
- else if (ts.isDoStatement(node)) {
739
+ else if (isDoStatement(node)) {
284
740
  const condition = `\tif !(${visit(node.expression, {
285
741
  inline: true
286
742
  })}) {\n\t\t\tbreak \n\t\t}\n\t`;
287
743
  return `for ${visit(node.statement, { inline: true, extraBlockContent: condition })}`;
288
744
  }
289
- else if (ts.isIfStatement(node)) {
290
- const condition = `if ${visit(node.expression, { inline: true })} ${visit(node.thenStatement, {
291
- inline: !!node.elseStatement
292
- })}`;
745
+ else if (isIfStatement(node)) {
746
+ // Go requires the branch body to be a block even for single statements
747
+ // Go requires a block body; `} else` must stay on the same line, so the
748
+ // terminating ';' is only added when no else branch follows
749
+ const thenTerm = node.elseStatement ? '' : ';';
750
+ const thenCode = isBlock(node.thenStatement)
751
+ ? visit(node.thenStatement, { inline: !!node.elseStatement })
752
+ : `{\n${visit(node.thenStatement)}\n}${thenTerm}`;
753
+ const condition = `if ${visit(node.expression, { inline: true })} ${thenCode}`;
293
754
  if (node.elseStatement) {
294
- return `${condition} else ${visit(node.elseStatement)}`;
755
+ if (isBlock(node.elseStatement)) {
756
+ return `${condition} else ${visit(node.elseStatement)}`;
757
+ }
758
+ // else-if chains stay chained; other single statements get a block
759
+ const elseCode = isIfStatement(node.elseStatement)
760
+ ? visit(node.elseStatement)
761
+ : `{\n${visit(node.elseStatement)}\n}`;
762
+ return `${condition} else ${elseCode}`;
295
763
  }
296
764
  return condition;
297
765
  }
298
- else if (ts.isSwitchStatement(node)) {
766
+ else if (isSwitchStatement(node)) {
299
767
  return `switch ${visit(node.expression)} ${visit(node.caseBlock)}`;
300
768
  }
301
- else if (ts.isCaseBlock(node)) {
302
- return `{\n\t\t${node.clauses.map((c) => visit(c)).join('\n\t\t')}\n\t}`;
769
+ else if (isCaseBlock(node)) {
770
+ return `{\n\t\t${(node.clauses ?? []).map((c) => visit(c)).join('\n\t\t')}\n\t}`;
303
771
  }
304
- else if (ts.isCaseClause(node)) {
305
- const isFallThrough = !node.statements.some((c) => ts.isBreakStatement(c));
306
- return `case ${visit(node.expression, { inline: true })}: \n\t\t\t${node.statements
307
- .filter((n) => !ts.isBreakStatement(n))
772
+ else if (isCaseClause(node)) {
773
+ const isFallThrough = !(node.statements ?? []).some((c) => isBreakStatement(c));
774
+ return `case ${visit(node.expression, { inline: true })}: \n\t\t\t${(node.statements ?? [])
775
+ .filter((n) => !isBreakStatement(n))
308
776
  .map((s) => visit(s))
309
777
  .join('')}${isFallThrough ? 'fallthrough\n\t' : ''}`;
310
778
  }
311
- else if (ts.isDefaultClause(node)) {
312
- return `default: \n\t\t\t${node.statements
313
- .filter((n) => !ts.isBreakStatement(n))
779
+ else if (isDefaultClause(node)) {
780
+ return `default: \n\t\t\t${(node.statements ?? [])
781
+ .filter((n) => !isBreakStatement(n))
314
782
  .map((s) => visit(s))
315
783
  .join('')}`;
316
784
  }
317
- else if (ts.isBreakStatement(node)) {
785
+ else if (isBreakStatement(node)) {
318
786
  return 'break';
319
787
  }
320
- else if (ts.isThrowStatement(node)) {
788
+ else if (isThrowStatement(node)) {
321
789
  const expr = node.expression;
322
- if (ts.isNewExpression(expr) &&
323
- ts.isIdentifier(expr.expression) &&
790
+ if (isNewExpression(expr) &&
791
+ isIdentifier(expr.expression) &&
324
792
  expr.expression.text === 'Error') {
325
793
  const args = expr.arguments ?? [];
326
794
  const msg = args.length > 0 ? visit(args[0]) : '""';
@@ -328,27 +796,27 @@ export function visit(node, options = {}) {
328
796
  }
329
797
  return `panic(${visit(expr)})` + (options.inline ? '' : ';\n\t');
330
798
  }
331
- else if (ts.isTryStatement(node)) {
799
+ else if (isTryStatement(node)) {
332
800
  return visitTryStatement(node, options);
333
801
  }
334
- else if (ts.isReturnStatement(node)) {
802
+ else if (isReturnStatement(node)) {
335
803
  // Handle return new Promise(...)
336
804
  if (node.expression &&
337
- ts.isNewExpression(node.expression) &&
338
- ts.isIdentifier(node.expression.expression) &&
805
+ isNewExpression(node.expression) &&
806
+ isIdentifier(node.expression.expression) &&
339
807
  node.expression.expression.text === 'Promise') {
340
808
  return visitPromiseReturn(node.expression, options);
341
809
  }
342
810
  return (`return ${node.expression ? visit(node.expression) : ''}` + (options.inline ? '' : ';\n\t'));
343
811
  }
344
- else if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
812
+ else if (isFunctionDeclaration(node) || isFunctionExpression(node)) {
345
813
  if (options.addFunctionOutside) {
346
814
  outsideNodes.push(node);
347
815
  return '';
348
816
  }
349
817
  const typeParams = getTypeParameters(node.typeParameters);
350
- const parameterInfo = getFunctionParametersInfo(node.parameters);
351
- if (node.body && ts.isBlock(node.body)) {
818
+ const parameterInfo = getFunctionParametersInfo((node.parameters ?? []));
819
+ if (node.body && isBlock(node.body)) {
352
820
  prescanVariableDeclarations(node.body);
353
821
  }
354
822
  const inferredRetType = inferFunctionBodyReturnType(node);
@@ -371,24 +839,24 @@ export function visit(node, options = {}) {
371
839
  prefixBlockContent: parameterInfo.prefixBlockContent
372
840
  })}`;
373
841
  }
374
- else if (ts.isArrowFunction(node)) {
375
- const parameterInfo = getFunctionParametersInfo(node.parameters);
842
+ else if (isArrowFunction(node)) {
843
+ const parameterInfo = getFunctionParametersInfo((node.parameters ?? []));
376
844
  const inferredRetType = inferFunctionBodyReturnType(node);
377
845
  const returnType = inferredRetType ? ` ${inferredRetType}` : '';
378
- if (parameterInfo.prefixBlockContent && !ts.isBlock(node.body)) {
846
+ if (parameterInfo.prefixBlockContent && !isBlock(node.body)) {
379
847
  return `func(${parameterInfo.signature})${returnType} {\n\t\t${parameterInfo.prefixBlockContent}return ${visit(node.body)};\n\t}`;
380
848
  }
381
- if (!ts.isBlock(node.body)) {
849
+ if (!isBlock(node.body)) {
382
850
  return `func(${parameterInfo.signature})${returnType} { return ${visit(node.body)}; }`;
383
851
  }
384
852
  return `func(${parameterInfo.signature})${returnType} ${visit(node.body, {
385
853
  prefixBlockContent: parameterInfo.prefixBlockContent
386
854
  })}`;
387
855
  }
388
- else if (node.kind === ts.SyntaxKind.ThisKeyword) {
856
+ else if (node.kind === 'ThisKeyword') {
389
857
  return 'self';
390
858
  }
391
- else if (ts.isEnumDeclaration(node)) {
859
+ else if (isEnumDeclaration(node)) {
392
860
  const enumName = node.name.text;
393
861
  enumNames.add(enumName);
394
862
  enumBaseTypes.set(enumName, getEnumBaseType(node));
@@ -398,16 +866,16 @@ export function visit(node, options = {}) {
398
866
  }
399
867
  return visitEnumDeclaration(node);
400
868
  }
401
- else if (ts.isTypeAliasDeclaration(node)) {
869
+ else if (isTypeAliasDeclaration(node)) {
402
870
  typeAliases.set(node.name.text, node.type);
403
871
  return '';
404
872
  }
405
- else if (ts.isInterfaceDeclaration(node)) {
873
+ else if (isInterfaceDeclaration(node)) {
406
874
  if (options.addFunctionOutside) {
407
875
  outsideNodes.push(node);
408
876
  const properties = new Map();
409
- for (const member of node.members) {
410
- if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
877
+ for (const member of (node.members ?? [])) {
878
+ if (isPropertySignature(member) && isIdentifier(member.name)) {
411
879
  properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
412
880
  }
413
881
  }
@@ -419,10 +887,10 @@ export function visit(node, options = {}) {
419
887
  const name = visit(node.name);
420
888
  const typeParams = getTypeParameters(node.typeParameters);
421
889
  const extendedInterfaces = [];
422
- if (node.heritageClauses) {
423
- for (const clause of node.heritageClauses) {
424
- if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
425
- for (const type of clause.types) {
890
+ if ((node.heritageClauses ?? [])) {
891
+ for (const clause of (node.heritageClauses ?? [])) {
892
+ if (clause.token?.kind === 'ExtendsKeyword') {
893
+ for (const type of (clause.types ?? [])) {
426
894
  extendedInterfaces.push(visit(type.expression));
427
895
  }
428
896
  }
@@ -430,16 +898,16 @@ export function visit(node, options = {}) {
430
898
  }
431
899
  const methods = [];
432
900
  const properties = [];
433
- for (const member of node.members) {
434
- if (ts.isMethodSignature(member)) {
901
+ for (const member of (node.members ?? [])) {
902
+ if (isMethodSignature(member)) {
435
903
  const methodName = visit(member.name);
436
- const params = member.parameters
904
+ const params = (member.parameters ?? [])
437
905
  .map((p) => `${visit(p.name)} ${getType(p.type)}`)
438
906
  .join(', ');
439
907
  const returnType = member.type ? ` ${getType(member.type)}` : '';
440
908
  methods.push(`\t${methodName}(${params})${returnType}`);
441
909
  }
442
- else if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
910
+ else if (isPropertySignature(member) && isIdentifier(member.name)) {
443
911
  properties.push(`\t${member.name.text} ${getOptionalNodeType(member.type, !!member.questionToken)}`);
444
912
  }
445
913
  }
@@ -450,19 +918,31 @@ export function visit(node, options = {}) {
450
918
  const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
451
919
  return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}`;
452
920
  }
453
- else if (ts.isClassDeclaration(node)) {
921
+ else if (isClassDeclaration(node)) {
454
922
  if (options.addFunctionOutside) {
455
923
  outsideNodes.push(node);
456
924
  const className = visit(node.name);
457
925
  classNames.add(className);
458
926
  const properties = new Map();
459
927
  const methods = new Map();
460
- for (const member of node.members) {
461
- if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
462
- properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
928
+ for (const member of (node.members ?? [])) {
929
+ const memberModifiers = member.modifiers;
930
+ const isStatic = memberModifiers?.some((m) => m.kind === 'StaticKeyword');
931
+ if (isPropertyDeclaration(member) && isIdentifier(member.name)) {
932
+ if (isStatic) {
933
+ classStaticProps.add(`${className}.${member.name.text}`);
934
+ }
935
+ else {
936
+ properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
937
+ }
463
938
  }
464
- if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
465
- methods.set(member.name.text, member.type ? getType(member.type) : 'interface{}');
939
+ if (isMethodDeclaration(member) && isIdentifier(member.name)) {
940
+ if (isStatic) {
941
+ classStaticMethods.add(`${className}.${member.name.text}`);
942
+ }
943
+ else {
944
+ methods.set(member.name.text, member.type ? getType(member.type) : 'interface{}');
945
+ }
466
946
  }
467
947
  }
468
948
  classPropertyTypes.set(className, properties);
@@ -473,9 +953,9 @@ export function visit(node, options = {}) {
473
953
  const typeParams = getTypeParameters(node.typeParameters);
474
954
  const typeParamNames = getTypeParameterNames(node.typeParameters);
475
955
  let parentClass = null;
476
- if (node.heritageClauses) {
477
- for (const clause of node.heritageClauses) {
478
- if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
956
+ if ((node.heritageClauses ?? [])) {
957
+ for (const clause of (node.heritageClauses ?? [])) {
958
+ if (clause.token?.kind === 'ExtendsKeyword') {
479
959
  parentClass = visit(clause.types[0].expression);
480
960
  }
481
961
  }
@@ -484,11 +964,11 @@ export function visit(node, options = {}) {
484
964
  if (parentClass) {
485
965
  fields.push(`\t${parentClass}`);
486
966
  }
487
- for (const member of node.members) {
488
- if (ts.isPropertyDeclaration(member)) {
967
+ for (const member of (node.members ?? [])) {
968
+ if (isPropertyDeclaration(member)) {
489
969
  const fieldName = visit(member.name);
490
970
  let fieldType;
491
- if (member.type && ts.isArrayTypeNode(member.type)) {
971
+ if (member.type && isArrayTypeNode(member.type)) {
492
972
  fieldType = `[]${getType(member.type, true)}`;
493
973
  }
494
974
  else {
@@ -498,13 +978,13 @@ export function visit(node, options = {}) {
498
978
  }
499
979
  }
500
980
  let result = `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}\n\n`;
501
- const ctor = node.members.find((m) => ts.isConstructorDeclaration(m));
981
+ const ctor = (node.members ?? []).find((m) => isConstructorDeclaration(m));
502
982
  if (ctor) {
503
- const ctorParameterInfo = getFunctionParametersInfo(ctor.parameters);
983
+ const ctorParameterInfo = getFunctionParametersInfo(ctor.parameters ?? []);
504
984
  const bodyStatements = ctor.body?.statements
505
985
  .filter((s) => {
506
- if (ts.isExpressionStatement(s) && ts.isCallExpression(s.expression)) {
507
- return s.expression.expression.kind !== ts.SyntaxKind.SuperKeyword;
986
+ if (isExpressionStatement(s) && isCallExpression(s.expression)) {
987
+ return s.expression.expression.kind !== 'SuperKeyword';
508
988
  }
509
989
  return true;
510
990
  })
@@ -515,19 +995,46 @@ export function visit(node, options = {}) {
515
995
  else {
516
996
  result += `func New${name}${typeParams}() *${name}${typeParamNames} {\n\t\treturn &${name}${typeParamNames}{}\n\t}\n\n`;
517
997
  }
518
- for (const member of node.members) {
519
- if (ts.isMethodDeclaration(member)) {
998
+ for (const member of (node.members ?? [])) {
999
+ if (isMethodDeclaration(member)) {
520
1000
  const methodName = visit(member.name);
521
- const methodParameterInfo = getFunctionParametersInfo(member.parameters);
1001
+ const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
522
1002
  const returnType = member.type ? ` ${getType(member.type)}` : '';
523
- result += `func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, {
524
- prefixBlockContent: methodParameterInfo.prefixBlockContent
525
- })}\n\n`;
1003
+ const isStatic = member.modifiers?.some((m) => m.kind === 'StaticKeyword');
1004
+ if (isStatic) {
1005
+ // Static methods become package-level functions named ClassName_methodName
1006
+ result += `func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
1007
+ }
1008
+ else {
1009
+ result += `func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
1010
+ }
1011
+ }
1012
+ else if (isGetAccessor(member)) {
1013
+ // Getter: get prop() { ... } → func (self *T) Prop() RetType { ... }
1014
+ const getterName = visit(member.name);
1015
+ const returnType = member.type ? ` ${getType(member.type)}` : ' interface{}';
1016
+ result += `func (self *${name}${typeParamNames}) Get_${getterName}()${returnType} ${visit(member.body)}\n\n`;
1017
+ }
1018
+ else if (isSetAccessor(member)) {
1019
+ // Setter: set prop(val) { ... } → func (self *T) SetProp(val ValType) { ... }
1020
+ const setterName = visit(member.name);
1021
+ const parameterInfo = getFunctionParametersInfo(member.parameters ?? []);
1022
+ result += `func (self *${name}${typeParamNames}) Set_${setterName}(${parameterInfo.signature}) ${visit(member.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}\n\n`;
1023
+ }
1024
+ }
1025
+ // Static property declarations → package-level vars named ClassName_propName
1026
+ for (const member of (node.members ?? [])) {
1027
+ if (isPropertyDeclaration(member) &&
1028
+ member.modifiers?.some((m) => m.kind === 'StaticKeyword')) {
1029
+ const fieldName = visit(member.name);
1030
+ const fieldType = getOptionalNodeType(member.type, !!member.questionToken);
1031
+ const initializer = member.initializer ? ` = ${visit(member.initializer)}` : '';
1032
+ result += `var ${name}_${fieldName} ${fieldType}${initializer}\n\n`;
526
1033
  }
527
1034
  }
528
1035
  return result.trim();
529
1036
  }
530
- else if (ts.isNewExpression(node)) {
1037
+ else if (isNewExpression(node)) {
531
1038
  const className = visit(node.expression);
532
1039
  if (className === 'Promise') {
533
1040
  return visitNewPromise(node);
@@ -535,7 +1042,7 @@ export function visit(node, options = {}) {
535
1042
  if (className === 'RegExp') {
536
1043
  importedPackages.add('regexp');
537
1044
  const nodeArgs = node.arguments ?? [];
538
- if (nodeArgs.length >= 2 && ts.isStringLiteral(nodeArgs[1])) {
1045
+ if (nodeArgs.length >= 2 && isStringLiteral(nodeArgs[1])) {
539
1046
  const pattern = visit(nodeArgs[0]);
540
1047
  const flags = jsRegexFlagsToGo(nodeArgs[1].text);
541
1048
  return `regexp.MustCompile("${flags}" + ${pattern})`;
@@ -551,51 +1058,58 @@ export function visit(node, options = {}) {
551
1058
  if (className === 'Set') {
552
1059
  return visitNewSet(node);
553
1060
  }
554
- const typeArgs = getTypeArguments(node.typeArguments);
555
- const args = node.arguments ? node.arguments.map((a) => visit(a)) : [];
1061
+ const typeArgs = getTypeArguments((node.typeArguments ?? []));
1062
+ const args = node.arguments ? (node.arguments ?? []).map((a) => visit(a)) : [];
556
1063
  return `New${className}${typeArgs}(${args.join(', ')})`;
557
1064
  }
558
- else if (ts.isObjectLiteralExpression(node)) {
1065
+ else if (isObjectLiteralExpression(node)) {
559
1066
  let typeName = '';
560
- if (ts.isVariableDeclaration(node.parent) && node.parent.type) {
1067
+ if (isVariableDeclaration(node.parent) && node.parent.type) {
561
1068
  typeName = getTypeText(node.parent.type);
562
1069
  }
563
- const properties = node.properties
1070
+ const properties = (node.properties ?? [])
564
1071
  .map((p) => {
565
- if (ts.isPropertyAssignment(p)) {
1072
+ if (isPropertyAssignment(p)) {
566
1073
  return `${visit(p.name)}: ${visit(p.initializer)}`;
567
1074
  }
1075
+ // Shorthand: { name } → name: name
1076
+ if (isShorthandPropertyAssignment(p)) {
1077
+ const name = visit(p.name);
1078
+ return `${name}: ${name}`;
1079
+ }
1080
+ // Spread: { ...obj } — not easily supported in Go structs, omit
568
1081
  return '';
569
1082
  })
570
1083
  .filter((p) => p)
571
1084
  .join(', ');
572
1085
  return `${typeName}{${properties}}`;
573
1086
  }
574
- else if (ts.isPropertyAssignment(node)) {
1087
+ else if (isPropertyAssignment(node)) {
575
1088
  return `${visit(node.name)}: ${visit(node.initializer)}`;
576
1089
  }
577
- else if (ts.isNonNullExpression(node)) {
1090
+ else if (isNonNullExpression(node)) {
578
1091
  return visit(node.expression);
579
1092
  }
580
- else if (ts.isImportDeclaration(node)) {
1093
+ else if (isImportDeclaration(node)) {
581
1094
  return visitImportDeclaration(node);
582
1095
  }
583
- else if (ts.isExportDeclaration(node) || ts.isExportAssignment(node)) {
1096
+ else if (isExportDeclaration(node) || isExportAssignment(node)) {
584
1097
  return '';
585
1098
  }
586
- const syntaxKind = ts.SyntaxKind[node.kind];
1099
+ const syntaxKind = node.kind;
587
1100
  if (!['FirstStatement', 'EndOfFileToken'].includes(syntaxKind)) {
588
- console.log(ts.SyntaxKind[node.kind], node.getText());
1101
+ const snippet = String(node.text ?? node.kind).substring(0, 60).replace(/\n/g, ' ');
1102
+ console.warn(`[TypeNative] Unsupported syntax: ${syntaxKind} — "${snippet}"`);
1103
+ }
1104
+ for (const child of childNodes(node)) {
1105
+ code += visit(child);
589
1106
  }
590
- ts.forEachChild(node, (subNode) => {
591
- code += visit(subNode);
592
- });
593
1107
  return code;
594
1108
  }
595
1109
  function getTypeText(typeNode) {
596
1110
  if (!typeNode)
597
1111
  return ':';
598
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1112
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
599
1113
  return typeNode.typeName.text;
600
1114
  }
601
1115
  return getType(typeNode);
@@ -603,6 +1117,34 @@ function getTypeText(typeNode) {
603
1117
  function toGoStringLiteral(value) {
604
1118
  return JSON.stringify(value);
605
1119
  }
1120
+ function visitSpreadArrayLiteral(node, elemType) {
1121
+ const chunks = [];
1122
+ for (const el of (node.elements ?? [])) {
1123
+ if (isSpreadElement(el)) {
1124
+ chunks.push({ isSpread: true, items: [el.expression] });
1125
+ }
1126
+ else {
1127
+ const last = chunks[chunks.length - 1];
1128
+ if (last && !last.isSpread) {
1129
+ last.items.push(el);
1130
+ }
1131
+ else {
1132
+ chunks.push({ isSpread: false, items: [el] });
1133
+ }
1134
+ }
1135
+ }
1136
+ const baseType = elemType || 'interface{}';
1137
+ let result = `[]${baseType}{}`;
1138
+ for (const chunk of chunks) {
1139
+ if (chunk.isSpread) {
1140
+ result = `append(${result}, ${visit(chunk.items[0])}...)`;
1141
+ }
1142
+ else {
1143
+ result = `append(${result}, ${chunk.items.map((e) => visit(e)).join(', ')})`;
1144
+ }
1145
+ }
1146
+ return result;
1147
+ }
606
1148
  function visitTemplateExpression(node) {
607
1149
  const parts = [];
608
1150
  if (node.head.text.length > 0) {
@@ -629,16 +1171,16 @@ function getTempName(prefix) {
629
1171
  }
630
1172
  function inferExpectedTypeFromContext(node) {
631
1173
  const parent = node.parent;
632
- if (ts.isVariableDeclaration(parent) && parent.initializer === node && parent.type) {
1174
+ if (isVariableDeclaration(parent) && parent.initializer === node && parent.type) {
633
1175
  return getType(parent.type);
634
1176
  }
635
- if (ts.isReturnStatement(parent)) {
1177
+ if (isReturnStatement(parent)) {
636
1178
  let scope = parent.parent;
637
1179
  while (scope) {
638
- if (ts.isFunctionDeclaration(scope) ||
639
- ts.isMethodDeclaration(scope) ||
640
- ts.isFunctionExpression(scope) ||
641
- ts.isArrowFunction(scope)) {
1180
+ if (isFunctionDeclaration(scope) ||
1181
+ isMethodDeclaration(scope) ||
1182
+ isFunctionExpression(scope) ||
1183
+ isArrowFunction(scope)) {
642
1184
  if (scope.type)
643
1185
  return getType(scope.type);
644
1186
  break;
@@ -650,9 +1192,9 @@ function inferExpectedTypeFromContext(node) {
650
1192
  }
651
1193
  function prescanVariableDeclarations(block) {
652
1194
  for (const stmt of block.statements) {
653
- if (ts.isVariableStatement(stmt)) {
1195
+ if (isVariableStatement(stmt)) {
654
1196
  for (const decl of stmt.declarationList.declarations) {
655
- if (ts.isIdentifier(decl.name) && !variableGoTypes.has(decl.name.text)) {
1197
+ if (isIdentifier(decl.name) && !variableGoTypes.has(decl.name.text)) {
656
1198
  if (decl.type) {
657
1199
  variableGoTypes.set(decl.name.text, getType(decl.type));
658
1200
  }
@@ -669,12 +1211,12 @@ function prescanVariableDeclarations(block) {
669
1211
  function inferFunctionBodyReturnType(node) {
670
1212
  if (node.type)
671
1213
  return getType(node.type);
672
- if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
1214
+ if (isArrowFunction(node) && !isBlock(node.body)) {
673
1215
  return inferExpressionType(node.body);
674
1216
  }
675
- if (node.body && ts.isBlock(node.body)) {
1217
+ if (node.body && isBlock(node.body)) {
676
1218
  for (const stmt of node.body.statements) {
677
- if (ts.isReturnStatement(stmt) && stmt.expression) {
1219
+ if (isReturnStatement(stmt) && stmt.expression) {
678
1220
  const exprType = inferExpressionType(stmt.expression);
679
1221
  if (exprType)
680
1222
  return exprType;
@@ -684,42 +1226,43 @@ function inferFunctionBodyReturnType(node) {
684
1226
  return undefined;
685
1227
  }
686
1228
  function inferArrowFunctionGoType(node) {
687
- const params = node.parameters.map((p) => (p.type ? getType(p.type) : 'interface{}')).join(', ');
1229
+ const params = (node.parameters ?? []).map((p) => (p.type ? getType(p.type) : 'interface{}')).join(', ');
688
1230
  const retType = inferFunctionBodyReturnType(node);
689
1231
  return `func(${params})${retType ? ` ${retType}` : ''}`;
690
1232
  }
691
1233
  function inferExpressionType(expr) {
692
- if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
1234
+ if (isArrowFunction(expr) || isFunctionExpression(expr)) {
693
1235
  return inferArrowFunctionGoType(expr);
694
1236
  }
695
- if (ts.isParenthesizedExpression(expr))
1237
+ if (isParenthesizedExpression(expr))
696
1238
  return inferExpressionType(expr.expression);
697
- if (ts.isNonNullExpression(expr))
1239
+ if (isNonNullExpression(expr))
698
1240
  return inferExpressionType(expr.expression);
699
- if (ts.isAsExpression(expr))
1241
+ if (isAsExpression(expr))
700
1242
  return getType(expr.type);
701
- if (ts.isTypeAssertionExpression(expr))
1243
+ if (isTypeAssertionExpression(expr))
702
1244
  return getType(expr.type);
703
- if (ts.isStringLiteral(expr) ||
704
- ts.isNoSubstitutionTemplateLiteral(expr) ||
705
- ts.isTemplateExpression(expr)) {
1245
+ if (isStringLiteral(expr) ||
1246
+ isNoSubstitutionTemplateLiteral(expr) ||
1247
+ isTemplateExpression(expr)) {
706
1248
  return 'string';
707
1249
  }
708
- if (ts.isNumericLiteral(expr))
1250
+ if (isNumericLiteral(expr))
709
1251
  return 'float64';
710
- if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword)
1252
+ if (expr.kind === 'TrueKeyword' || expr.kind === 'FalseKeyword')
711
1253
  return 'bool';
712
- if (expr.kind === ts.SyntaxKind.NullKeyword)
1254
+ if (expr.kind === 'NullKeyword')
713
1255
  return 'nil';
714
- if (ts.isIdentifier(expr))
1256
+ if (isIdentifier(expr))
715
1257
  return variableGoTypes.get(expr.text);
716
- if (ts.isArrayLiteralExpression(expr)) {
717
- if (expr.elements.length === 0)
1258
+ if (isArrayLiteralExpression(expr)) {
1259
+ const elements = expr.elements ?? [];
1260
+ if (elements.length === 0)
718
1261
  return '[]interface{}';
719
- const firstElementType = inferExpressionType(expr.elements[0]) ?? 'interface{}';
1262
+ const firstElementType = inferExpressionType(elements[0]) ?? 'interface{}';
720
1263
  return `[]${firstElementType}`;
721
1264
  }
722
- if (ts.isNewExpression(expr) && ts.isIdentifier(expr.expression)) {
1265
+ if (isNewExpression(expr) && isIdentifier(expr.expression)) {
723
1266
  const ctorName = expr.expression.text;
724
1267
  if (ctorName === 'Map' && expr.typeArguments && expr.typeArguments.length === 2) {
725
1268
  return `map[${getType(expr.typeArguments[0])}]${getType(expr.typeArguments[1])}`;
@@ -728,8 +1271,8 @@ function inferExpressionType(expr) {
728
1271
  return `map[${getType(expr.typeArguments[0])}]struct{}`;
729
1272
  }
730
1273
  }
731
- if (ts.isPropertyAccessExpression(expr)) {
732
- if (ts.isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
1274
+ if (isPropertyAccessExpression(expr)) {
1275
+ if (isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
733
1276
  const enumType = getSafeName(expr.expression.text);
734
1277
  return enumType;
735
1278
  }
@@ -751,7 +1294,7 @@ function inferExpressionType(expr) {
751
1294
  if (resolvedPropertyType) {
752
1295
  return resolvedPropertyType;
753
1296
  }
754
- if (ts.isIdentifier(expr.expression)) {
1297
+ if (isIdentifier(expr.expression)) {
755
1298
  const className = variableClassNames.get(expr.expression.text);
756
1299
  const memberType = className
757
1300
  ? classPropertyTypes.get(className)?.get(expr.name.text)
@@ -760,7 +1303,7 @@ function inferExpressionType(expr) {
760
1303
  return memberType;
761
1304
  }
762
1305
  }
763
- if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
1306
+ if (isCallExpression(expr) && isPropertyAccessExpression(expr.expression)) {
764
1307
  const methodName = expr.expression.name.text;
765
1308
  const ownerType = inferExpressionType(expr.expression.expression);
766
1309
  if (ownerType && ownerType.startsWith('map[')) {
@@ -797,7 +1340,7 @@ function inferExpressionType(expr) {
797
1340
  return returnType;
798
1341
  }
799
1342
  }
800
- if (ts.isIdentifier(expr.expression.expression)) {
1343
+ if (isIdentifier(expr.expression.expression)) {
801
1344
  const className = variableClassNames.get(expr.expression.expression.text);
802
1345
  const returnType = className
803
1346
  ? classMethodReturnTypes.get(className)?.get(methodName)
@@ -810,15 +1353,15 @@ function inferExpressionType(expr) {
810
1353
  }
811
1354
  }
812
1355
  }
813
- if (ts.isConditionalExpression(expr)) {
1356
+ if (isConditionalExpression(expr)) {
814
1357
  const whenTrueType = inferExpressionType(expr.whenTrue);
815
1358
  const whenFalseType = inferExpressionType(expr.whenFalse);
816
1359
  if (whenTrueType && whenTrueType === whenFalseType)
817
1360
  return whenTrueType;
818
1361
  return whenTrueType ?? whenFalseType;
819
1362
  }
820
- if (ts.isBinaryExpression(expr) &&
821
- expr.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
1363
+ if (isBinaryExpression(expr) &&
1364
+ expr.operatorToken.kind === 'QuestionQuestionToken') {
822
1365
  const leftType = inferExpressionType(expr.left);
823
1366
  const rightType = inferExpressionType(expr.right);
824
1367
  if (leftType && leftType.startsWith('*') && rightType === leftType.slice(1)) {
@@ -898,14 +1441,14 @@ function visitOptionalElementAccess(node) {
898
1441
  return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${elementExpr} }()`;
899
1442
  }
900
1443
  function visitOptionalCall(node) {
901
- if (!ts.isPropertyAccessExpression(node.expression)) {
902
- return `${visit(node.expression)}(${node.arguments.map((a) => visit(a)).join(', ')})`;
1444
+ if (!isPropertyAccessExpression(node.expression)) {
1445
+ return `${visit(node.expression)}(${(node.arguments ?? []).map((a) => visit(a)).join(', ')})`;
903
1446
  }
904
1447
  const baseNode = node.expression.expression;
905
1448
  const methodName = node.expression.name.text;
906
1449
  const baseExpr = visit(baseNode);
907
1450
  const baseType = inferExpressionType(baseNode);
908
- const args = node.arguments.map((a) => visit(a)).join(', ');
1451
+ const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
909
1452
  if (!baseType || !baseType.startsWith('*')) {
910
1453
  return `${baseExpr}.${methodName}(${args})`;
911
1454
  }
@@ -930,18 +1473,18 @@ function getArrayElementTypeFromGoType(goType) {
930
1473
  return elementType || 'interface{}';
931
1474
  }
932
1475
  function inferArrayCallbackReturnType(callback, elementType, fallbackType) {
933
- if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
1476
+ if (isArrowFunction(callback) || isFunctionExpression(callback)) {
934
1477
  if (callback.type) {
935
1478
  const explicitType = getType(callback.type);
936
1479
  return explicitType || fallbackType;
937
1480
  }
938
- if (ts.isBlock(callback.body)) {
1481
+ if (isBlock(callback.body)) {
939
1482
  return fallbackType;
940
1483
  }
941
1484
  const inferred = inferExpressionType(callback.body);
942
1485
  return inferred ?? fallbackType;
943
1486
  }
944
- if (ts.isIdentifier(callback)) {
1487
+ if (isIdentifier(callback)) {
945
1488
  const knownType = variableGoTypes.get(callback.text);
946
1489
  if (knownType)
947
1490
  return knownType;
@@ -949,7 +1492,7 @@ function inferArrayCallbackReturnType(callback, elementType, fallbackType) {
949
1492
  return fallbackType;
950
1493
  }
951
1494
  function buildArrayCallbackInfo(callback, elementType, forcedReturnType) {
952
- if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
1495
+ if (isArrowFunction(callback) || isFunctionExpression(callback)) {
953
1496
  const paramCount = callback.parameters.length;
954
1497
  const callbackReturnType = forcedReturnType ?? inferArrayCallbackReturnType(callback, elementType, 'interface{}');
955
1498
  const params = [];
@@ -962,9 +1505,9 @@ function buildArrayCallbackInfo(callback, elementType, forcedReturnType) {
962
1505
  if (paramCount > 2) {
963
1506
  params.push(`${visit(callback.parameters[2].name)} []${elementType}`);
964
1507
  }
965
- const body = ts.isBlock(callback.body)
966
- ? visit(callback.body)
967
- : `{\n\t\treturn ${visit(callback.body)};\n\t}`;
1508
+ const body = isBlock(callback.body)
1509
+ ? visit(callback.body, { inline: true })
1510
+ : `{ return ${visit(callback.body)}; }`;
968
1511
  return {
969
1512
  fnExpr: `func(${params.join(', ')}) ${callbackReturnType} ${body}`,
970
1513
  paramCount,
@@ -987,11 +1530,24 @@ function buildArrayCallbackInvocation(callbackInfo, itemVar, indexVar, arrayVar)
987
1530
  args.push(arrayVar);
988
1531
  return `(${callbackInfo.fnExpr})(${args.join(', ')})`;
989
1532
  }
1533
+ // Like buildArrayCallbackInvocation but for reduce: (acc, item, idx, arr)
1534
+ function buildArrayCallbackInvocationReduce(callbackInfo, accVar, itemVar, indexVar, arrayVar) {
1535
+ const args = [];
1536
+ if (callbackInfo.paramCount > 0)
1537
+ args.push(accVar);
1538
+ if (callbackInfo.paramCount > 1)
1539
+ args.push(itemVar);
1540
+ if (callbackInfo.paramCount > 2)
1541
+ args.push(`float64(${indexVar})`);
1542
+ if (callbackInfo.paramCount > 3)
1543
+ args.push(arrayVar);
1544
+ return `(${callbackInfo.fnExpr})(${args.join(', ')})`;
1545
+ }
990
1546
  function visitArrayHigherOrderCall(node) {
991
- if (!ts.isPropertyAccessExpression(node.expression))
1547
+ if (!isPropertyAccessExpression(node.expression))
992
1548
  return undefined;
993
1549
  const methodName = node.expression.name.text;
994
- if (!['map', 'filter', 'some', 'find', 'join'].includes(methodName)) {
1550
+ if (!['map', 'filter', 'some', 'find', 'findIndex', 'every', 'forEach', 'reduce', 'join'].includes(methodName)) {
995
1551
  return undefined;
996
1552
  }
997
1553
  const arrayExprNode = node.expression.expression;
@@ -1006,12 +1562,12 @@ function visitArrayHigherOrderCall(node) {
1006
1562
  return undefined;
1007
1563
  importedPackages.add('strings');
1008
1564
  importedPackages.add('fmt');
1009
- const separator = node.arguments[0] ? visit(node.arguments[0]) : '""';
1565
+ const separator = (node.arguments ?? [])[0] ? visit((node.arguments ?? [])[0]) : '""';
1010
1566
  const arrVar = getTempName('arrjoin');
1011
1567
  const partsVar = getTempName('parts');
1012
1568
  return `func() string { ${arrVar} := ${arrayExpr}; ${partsVar} := make([]string, len(${arrVar})); for i, v := range ${arrVar} { ${partsVar}[i] = fmt.Sprintf("%v", v) }; return strings.Join(${partsVar}, ${separator}) }()`;
1013
1569
  }
1014
- const callback = node.arguments[0];
1570
+ const callback = (node.arguments ?? [])[0];
1015
1571
  if (!callback) {
1016
1572
  return undefined;
1017
1573
  }
@@ -1039,6 +1595,43 @@ function visitArrayHigherOrderCall(node) {
1039
1595
  const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
1040
1596
  return `func() bool { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return true } }; return false }()`;
1041
1597
  }
1598
+ if (methodName === 'findIndex') {
1599
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
1600
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
1601
+ return `func() float64 { ${arrVar} := ${arrayExpr}; for ${idxVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return float64(${idxVar}) } }; return float64(-1) }()`;
1602
+ }
1603
+ if (methodName === 'every') {
1604
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
1605
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
1606
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
1607
+ return `func() bool { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if !(${callbackCall}) { return false } }; return true }()`;
1608
+ }
1609
+ if (methodName === 'forEach') {
1610
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, '');
1611
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
1612
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
1613
+ return `func() { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${callbackCall} } }()`;
1614
+ }
1615
+ if (methodName === 'reduce') {
1616
+ const reduceCallback = (node.arguments ?? [])[0];
1617
+ if (!reduceCallback)
1618
+ return undefined;
1619
+ const initialValue = (node.arguments ?? [])[1] ? visit((node.arguments ?? [])[1]) : undefined;
1620
+ const accType = initialValue
1621
+ ? (inferExpressionType((node.arguments ?? [])[1]) ?? elementType)
1622
+ : elementType;
1623
+ const accVar = getTempName('acc');
1624
+ // Build a callback that takes (acc, item, idx, arr) — param count determines what gets passed
1625
+ const cbInfo = buildArrayCallbackInfo(reduceCallback, elementType, accType ?? 'interface{}');
1626
+ const cbCall = buildArrayCallbackInvocationReduce(cbInfo, accVar, itemVar, idxVar, arrVar);
1627
+ // Only include the index loop var if the callback uses it (paramCount > 2)
1628
+ const reduceIdxVar = cbInfo.paramCount > 2 ? idxVar : '_';
1629
+ if (initialValue !== undefined) {
1630
+ return `func() ${accType} { ${arrVar} := ${arrayExpr}; ${accVar} := ${initialValue}; for ${reduceIdxVar}, ${itemVar} := range ${arrVar} { ${accVar} = ${cbCall} }; return ${accVar} }()`;
1631
+ }
1632
+ return `func() ${elementType} { ${arrVar} := ${arrayExpr}; ${accVar} := ${arrVar}[0]; for ${reduceIdxVar}, ${itemVar} := range ${arrVar}[1:] { ${accVar} = ${cbCall} }; return ${accVar} }()`;
1633
+ }
1634
+ // find
1042
1635
  const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
1043
1636
  const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
1044
1637
  const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
@@ -1050,7 +1643,7 @@ function getAliasType(name, seen = new Set()) {
1050
1643
  return undefined;
1051
1644
  if (seen.has(name))
1052
1645
  return undefined;
1053
- if (ts.isTypeReferenceNode(aliasType) && ts.isIdentifier(aliasType.typeName)) {
1646
+ if (isTypeReferenceNode(aliasType) && isIdentifier(aliasType.typeName)) {
1054
1647
  const nestedName = aliasType.typeName.text;
1055
1648
  if (typeAliases.has(nestedName)) {
1056
1649
  seen.add(name);
@@ -1070,33 +1663,33 @@ function getOptionalNodeType(typeNode, isOptional) {
1070
1663
  return baseType;
1071
1664
  }
1072
1665
  function getEnumMemberName(name) {
1073
- if (ts.isIdentifier(name)) {
1666
+ if (isIdentifier(name)) {
1074
1667
  return getSafeName(name.text);
1075
1668
  }
1076
- if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
1669
+ if (isStringLiteral(name) || isNumericLiteral(name)) {
1077
1670
  const sanitized = name.text.replace(/[^a-zA-Z0-9_]/g, '_');
1078
1671
  return sanitized.length > 0 ? sanitized : 'Member';
1079
1672
  }
1080
1673
  return 'Member';
1081
1674
  }
1082
1675
  function getEnumBaseType(node) {
1083
- for (const member of node.members) {
1676
+ for (const member of (node.members ?? [])) {
1084
1677
  const initializer = member.initializer;
1085
1678
  if (!initializer)
1086
1679
  continue;
1087
- if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {
1680
+ if (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer)) {
1088
1681
  return 'string';
1089
1682
  }
1090
1683
  }
1091
1684
  return 'float64';
1092
1685
  }
1093
1686
  function readNumericEnumInitializer(initializer) {
1094
- if (ts.isNumericLiteral(initializer)) {
1687
+ if (isNumericLiteral(initializer)) {
1095
1688
  return Number(initializer.text);
1096
1689
  }
1097
- if (ts.isPrefixUnaryExpression(initializer) &&
1098
- initializer.operator === ts.SyntaxKind.MinusToken &&
1099
- ts.isNumericLiteral(initializer.operand)) {
1690
+ if (isPrefixUnaryExpression(initializer) &&
1691
+ initializer.operator === 'MinusToken' &&
1692
+ isNumericLiteral(initializer.operand)) {
1100
1693
  return -Number(initializer.operand.text);
1101
1694
  }
1102
1695
  return undefined;
@@ -1107,7 +1700,7 @@ function visitEnumDeclaration(node) {
1107
1700
  let nextNumericValue = 0;
1108
1701
  let canAutoIncrement = true;
1109
1702
  const members = [];
1110
- for (const member of node.members) {
1703
+ for (const member of (node.members ?? [])) {
1111
1704
  const memberName = getEnumMemberName(member.name);
1112
1705
  const symbolName = `${enumName}_${memberName}`;
1113
1706
  let valueExpr;
@@ -1143,15 +1736,15 @@ function visitEnumDeclaration(node) {
1143
1736
  function getType(typeNode, getArrayType = false) {
1144
1737
  if (!typeNode)
1145
1738
  return ':';
1146
- if (ts.isArrayTypeNode(typeNode)) {
1739
+ if (isArrayTypeNode(typeNode)) {
1147
1740
  const elementType = getType(typeNode.elementType);
1148
1741
  return getArrayType ? elementType : `[]${elementType}`;
1149
1742
  }
1150
1743
  // Handle union types (e.g. string | null, number | undefined)
1151
- if (ts.isUnionTypeNode(typeNode)) {
1152
- const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1153
- t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1154
- !(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
1744
+ if (isUnionTypeNode(typeNode)) {
1745
+ const nonNullTypes = typeNode.types.filter((t) => t.kind !== 'NullKeyword' &&
1746
+ t.kind !== 'UndefinedKeyword' &&
1747
+ !(isLiteralTypeNode(t) && t.literal.kind === 'NullKeyword'));
1155
1748
  if (nonNullTypes.length === 1 && nonNullTypes.length < typeNode.types.length) {
1156
1749
  // This is a nullable type T | null or T | undefined
1157
1750
  const innerType = getType(nonNullTypes[0]);
@@ -1164,14 +1757,14 @@ function getType(typeNode, getArrayType = false) {
1164
1757
  // Non-nullable union or multi-type union → interface{}
1165
1758
  return 'interface{}';
1166
1759
  }
1167
- if (ts.isFunctionTypeNode(typeNode)) {
1760
+ if (isFunctionTypeNode(typeNode)) {
1168
1761
  const params = typeNode.parameters
1169
1762
  .map((p) => (p.type ? getType(p.type) : 'interface{}'))
1170
1763
  .join(', ');
1171
1764
  const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
1172
1765
  return `func(${params})${ret}`;
1173
1766
  }
1174
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1767
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
1175
1768
  const name = typeNode.typeName.text;
1176
1769
  if (enumNames.has(name)) {
1177
1770
  return getSafeName(name);
@@ -1201,8 +1794,8 @@ function getType(typeNode, getArrayType = false) {
1201
1794
  }
1202
1795
  return `${name}${typeArgs}`;
1203
1796
  }
1204
- const type = TypeCheker.getTypeFromTypeNode(typeNode);
1205
- let typeName = TypeCheker.typeToString(type);
1797
+ // Syntactic replacement for the ts typechecker: render the type node's text
1798
+ let typeName = typeNodeToText(typeNode);
1206
1799
  const isArray = typeName.includes('[]');
1207
1800
  if (isArray) {
1208
1801
  if (getArrayType) {
@@ -1228,22 +1821,22 @@ function getType(typeNode, getArrayType = false) {
1228
1821
  function getTypeCategory(typeNode) {
1229
1822
  if (!typeNode)
1230
1823
  return undefined;
1231
- if (ts.isArrayTypeNode(typeNode))
1824
+ if (isArrayTypeNode(typeNode))
1232
1825
  return 'array';
1233
- if (typeNode.kind === ts.SyntaxKind.StringKeyword)
1826
+ if (typeNode.kind === 'StringKeyword')
1234
1827
  return 'string';
1235
- if (typeNode.kind === ts.SyntaxKind.NumberKeyword)
1828
+ if (typeNode.kind === 'NumberKeyword')
1236
1829
  return 'number';
1237
- if (typeNode.kind === ts.SyntaxKind.BooleanKeyword)
1830
+ if (typeNode.kind === 'BooleanKeyword')
1238
1831
  return 'boolean';
1239
- if (ts.isUnionTypeNode(typeNode)) {
1240
- const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1241
- t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1242
- !(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
1832
+ if (isUnionTypeNode(typeNode)) {
1833
+ const nonNullTypes = typeNode.types.filter((t) => t.kind !== 'NullKeyword' &&
1834
+ t.kind !== 'UndefinedKeyword' &&
1835
+ !(isLiteralTypeNode(t) && t.literal.kind === 'NullKeyword'));
1243
1836
  if (nonNullTypes.length === 1)
1244
1837
  return getTypeCategory(nonNullTypes[0]);
1245
1838
  }
1246
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1839
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
1247
1840
  const name = typeNode.typeName.text;
1248
1841
  if (name === 'Map')
1249
1842
  return 'Map';
@@ -1256,13 +1849,13 @@ function getTypeCategory(typeNode) {
1256
1849
  return undefined;
1257
1850
  }
1258
1851
  function getClassNameFromTypeNode(typeNode) {
1259
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1852
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
1260
1853
  return classNames.has(typeNode.typeName.text) ? typeNode.typeName.text : undefined;
1261
1854
  }
1262
- if (ts.isUnionTypeNode(typeNode)) {
1263
- const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1264
- t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1265
- !(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
1855
+ if (isUnionTypeNode(typeNode)) {
1856
+ const nonNullTypes = typeNode.types.filter((t) => t.kind !== 'NullKeyword' &&
1857
+ t.kind !== 'UndefinedKeyword' &&
1858
+ !(isLiteralTypeNode(t) && t.literal.kind === 'NullKeyword'));
1266
1859
  if (nonNullTypes.length === 1) {
1267
1860
  return getClassNameFromTypeNode(nonNullTypes[0]);
1268
1861
  }
@@ -1270,18 +1863,18 @@ function getClassNameFromTypeNode(typeNode) {
1270
1863
  return undefined;
1271
1864
  }
1272
1865
  function resolveExpressionType(expr) {
1273
- if (ts.isIdentifier(expr)) {
1866
+ if (isIdentifier(expr)) {
1274
1867
  return variableTypes.get(expr.text);
1275
1868
  }
1276
- if (expr.kind === ts.SyntaxKind.ThisKeyword) {
1869
+ if (expr.kind === 'ThisKeyword') {
1277
1870
  return 'class';
1278
1871
  }
1279
1872
  return undefined;
1280
1873
  }
1281
1874
  function isNilLiteral(node) {
1282
- if (node.kind === ts.SyntaxKind.NullKeyword)
1875
+ if (node.kind === 'NullKeyword')
1283
1876
  return true;
1284
- if (ts.isIdentifier(node) && node.text === 'undefined')
1877
+ if (isIdentifier(node) && node.text === 'undefined')
1285
1878
  return true;
1286
1879
  return false;
1287
1880
  }
@@ -1292,6 +1885,26 @@ function getAcessString(leftSide, rightSide, objectType) {
1292
1885
  if (rightSide === 'size' && (objectType === 'Map' || objectType === 'Set')) {
1293
1886
  return `float64(len(${leftSide}))`;
1294
1887
  }
1888
+ // process global properties
1889
+ if (leftSide === 'process') {
1890
+ if (rightSide === 'argv') {
1891
+ importedPackages.add('os');
1892
+ return 'os.Args';
1893
+ }
1894
+ if (rightSide === 'platform') {
1895
+ importedPackages.add('runtime');
1896
+ return 'runtime.GOOS';
1897
+ }
1898
+ if (rightSide === 'env') {
1899
+ // process.env.X is detected via the nested access below
1900
+ return 'process.env';
1901
+ }
1902
+ }
1903
+ // process.env.X → TnGetenv("X") (os.Environ() is a []string in Go, not a map)
1904
+ if (leftSide === 'process.env') {
1905
+ useHelper('getenv');
1906
+ return `TnGetenv("${rightSide}")`;
1907
+ }
1295
1908
  return `${leftSide}.${rightSide}`;
1296
1909
  }
1297
1910
  const callHandlers = {
@@ -1355,7 +1968,84 @@ const callHandlers = {
1355
1968
  parseFloat: (_caller, args) => {
1356
1969
  importedPackages.add('strconv');
1357
1970
  return `func() float64 { v, _ := strconv.ParseFloat(${args[0]}, 64); return v }()`;
1358
- }
1971
+ },
1972
+ 'process.exit': (_caller, args) => {
1973
+ importedPackages.add('os');
1974
+ return `os.Exit(int(${args[0] ?? '0'}))`;
1975
+ },
1976
+ 'process.cwd': () => {
1977
+ importedPackages.add('os');
1978
+ return `func() string { __d, _ := os.Getwd(); return __d }()`;
1979
+ },
1980
+ 'JSON.stringify': (_caller, args) => {
1981
+ importedPackages.add('encoding/json');
1982
+ if (args.length >= 3) {
1983
+ return `func() string { __b, _ := json.MarshalIndent(${args[0]}, "", " "); return string(__b) }()`;
1984
+ }
1985
+ return `func() string { __b, _ := json.Marshal(${args[0]}); return string(__b) }()`;
1986
+ },
1987
+ 'JSON.parse': (_caller, args) => {
1988
+ importedPackages.add('encoding/json');
1989
+ return `func() interface{} { var __v interface{}; json.Unmarshal([]byte(${args[0]}), &__v); return __v }()`;
1990
+ },
1991
+ 'Object.keys': (_caller, args) => {
1992
+ importedPackages.add('maps');
1993
+ return `func() []string { __k := make([]string, 0); for k := range ${args[0]} { __k = append(__k, k) }; return __k }()`;
1994
+ },
1995
+ 'Object.values': (_caller, args) => {
1996
+ return `func() []interface{} { __v := make([]interface{}, 0); for _, v := range ${args[0]} { __v = append(__v, v) }; return __v }()`;
1997
+ },
1998
+ 'Object.entries': (_caller, args) => {
1999
+ // When used outside for...of, produce a slice of [key, value] pairs — rarely needed
2000
+ return args[0];
2001
+ },
2002
+ 'Math.log': (_caller, args) => {
2003
+ importedPackages.add('math');
2004
+ return `math.Log(${args[0]})`;
2005
+ },
2006
+ 'Math.log2': (_caller, args) => {
2007
+ importedPackages.add('math');
2008
+ return `math.Log2(${args[0]})`;
2009
+ },
2010
+ 'Math.log10': (_caller, args) => {
2011
+ importedPackages.add('math');
2012
+ return `math.Log10(${args[0]})`;
2013
+ },
2014
+ 'Math.sin': (_caller, args) => {
2015
+ importedPackages.add('math');
2016
+ return `math.Sin(${args[0]})`;
2017
+ },
2018
+ 'Math.cos': (_caller, args) => {
2019
+ importedPackages.add('math');
2020
+ return `math.Cos(${args[0]})`;
2021
+ },
2022
+ 'Math.tan': (_caller, args) => {
2023
+ importedPackages.add('math');
2024
+ return `math.Tan(${args[0]})`;
2025
+ },
2026
+ 'Math.trunc': (_caller, args) => {
2027
+ importedPackages.add('math');
2028
+ return `math.Trunc(${args[0]})`;
2029
+ },
2030
+ 'Math.sign': (_caller, args) => {
2031
+ return `func() float64 { if ${args[0]} > 0 { return 1 }; if ${args[0]} < 0 { return -1 }; return 0 }()`;
2032
+ },
2033
+ 'console.error': (_caller, args) => {
2034
+ importedPackages.add('fmt');
2035
+ importedPackages.add('os');
2036
+ return `fmt.Fprintln(os.Stderr, ${args.join(', ')})`;
2037
+ },
2038
+ 'String': (_caller, args) => {
2039
+ importedPackages.add('fmt');
2040
+ return `fmt.Sprintf("%v", ${args[0]})`;
2041
+ },
2042
+ 'Number': (_caller, args) => {
2043
+ importedPackages.add('strconv');
2044
+ return `func() float64 { v, _ := strconv.ParseFloat(fmt.Sprintf("%v", ${args[0]}), 64); return v }()`;
2045
+ },
2046
+ 'Boolean': (_caller, args) => {
2047
+ return `(${args[0]} != nil && ${args[0]} != false && ${args[0]} != 0 && ${args[0]} != "")`;
2048
+ },
1359
2049
  };
1360
2050
  const stringMethodHandlers = {
1361
2051
  split: (obj, args) => {
@@ -1422,6 +2112,32 @@ const stringMethodHandlers = {
1422
2112
  return `${obj}[int(${args[0]}):]`;
1423
2113
  },
1424
2114
  concat: (obj, args) => `${obj} + ${args.join(' + ')}`,
2115
+ padStart: (obj, args) => {
2116
+ importedPackages.add('fmt');
2117
+ importedPackages.add('strings');
2118
+ const pad = args[1] ?? '" "';
2119
+ return `func() string { __s := ${obj}; __n := int(${args[0]}) - len(__s); if __n > 0 { __s = strings.Repeat(${pad}, __n)[:__n] + __s }; return __s }()`;
2120
+ },
2121
+ padEnd: (obj, args) => {
2122
+ importedPackages.add('strings');
2123
+ const pad = args[1] ?? '" "';
2124
+ return `func() string { __s := ${obj}; __n := int(${args[0]}) - len(__s); if __n > 0 { __s = __s + strings.Repeat(${pad}, __n)[:__n] }; return __s }()`;
2125
+ },
2126
+ match: (obj, args) => {
2127
+ importedPackages.add('regexp');
2128
+ return `regexp.MustCompile(${args[0]}).FindStringSubmatch(${obj})`;
2129
+ },
2130
+ matchAll: (obj, args) => {
2131
+ importedPackages.add('regexp');
2132
+ return `regexp.MustCompile(${args[0]}).FindAllStringSubmatch(${obj}, -1)`;
2133
+ },
2134
+ search: (obj, args) => {
2135
+ importedPackages.add('regexp');
2136
+ return `float64(regexp.MustCompile(${args[0]}).FindStringIndex(${obj})[0])`;
2137
+ },
2138
+ at: (obj, args) => {
2139
+ return `func() string { __i := int(${args[0]}); if __i < 0 { __i = len(${obj}) + __i }; return string(${obj}[__i]) }()`;
2140
+ },
1425
2141
  toString: (obj) => {
1426
2142
  importedPackages.add('fmt');
1427
2143
  return `fmt.Sprintf("%v", ${obj})`;
@@ -1433,6 +2149,13 @@ const regexpMethodHandlers = {
1433
2149
  };
1434
2150
  const arrayMethodHandlers = {
1435
2151
  push: (obj, args) => `${obj} = append(${obj}, ${args.join(', ')})`,
2152
+ pop: (obj) => {
2153
+ return `func() interface{} { if len(${obj}) == 0 { return nil }; __last := ${obj}[len(${obj})-1]; ${obj} = ${obj}[:len(${obj})-1]; return __last }()`;
2154
+ },
2155
+ shift: (obj) => {
2156
+ return `func() interface{} { if len(${obj}) == 0 { return nil }; __first := ${obj}[0]; ${obj} = ${obj}[1:]; return __first }()`;
2157
+ },
2158
+ unshift: (obj, args) => `${obj} = append([]interface{}{${args.join(', ')}}, ${obj}...)`,
1436
2159
  join: (obj, args) => {
1437
2160
  importedPackages.add('strings');
1438
2161
  return `strings.Join(${obj}, ${args[0] ?? '""'})`;
@@ -1442,10 +2165,30 @@ const arrayMethodHandlers = {
1442
2165
  return `${obj}[int(${args[0]}):int(${args[1]})]`;
1443
2166
  return `${obj}[int(${args[0]}):]`;
1444
2167
  },
2168
+ reverse: (obj) => {
2169
+ importedPackages.add('slices');
2170
+ return `func() interface{} { slices.Reverse(${obj}); return nil }()`;
2171
+ },
2172
+ sort: (obj) => {
2173
+ importedPackages.add('sort');
2174
+ return `func() interface{} { sort.Slice(${obj}, func(i, j int) bool { return fmt.Sprintf("%v", ${obj}[i]) < fmt.Sprintf("%v", ${obj}[j]) }); return nil }()`;
2175
+ },
2176
+ indexOf: (obj, args) => {
2177
+ return `func() float64 { for __i, __v := range ${obj} { if fmt.Sprintf("%v", __v) == fmt.Sprintf("%v", ${args[0]}) { return float64(__i) } }; return float64(-1) }()`;
2178
+ },
2179
+ includes: (obj, args) => {
2180
+ return `func() bool { for _, __v := range ${obj} { if fmt.Sprintf("%v", __v) == fmt.Sprintf("%v", ${args[0]}) { return true } }; return false }()`;
2181
+ },
2182
+ concat: (obj, args) => `append(${obj}, ${args.join(', ')}...)`,
2183
+ flat: (obj) => obj,
1445
2184
  toString: (obj) => {
1446
2185
  importedPackages.add('fmt');
1447
2186
  return `fmt.Sprintf("%v", ${obj})`;
1448
- }
2187
+ },
2188
+ // padStart / padEnd for string arrays (rarely used, but added for completeness)
2189
+ at: (obj, args) => {
2190
+ 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] }()`;
2191
+ },
1449
2192
  };
1450
2193
  const mapMethodHandlers = {
1451
2194
  set: (obj, args) => `${obj}[${args[0]}] = ${args[1]}`,
@@ -1517,7 +2260,9 @@ function getDynamicCallHandler(caller, objectType) {
1517
2260
  return null;
1518
2261
  }
1519
2262
  function getCallString(caller, args, typeArgs = '', objectType) {
1520
- const handler = callHandlers[caller] ?? getDynamicCallHandler(caller, objectType);
2263
+ // Parentheses from casts like `(mod as any).fn(...)` are stripped for lookup
2264
+ const handler = callHandlers[caller] ?? callHandlers[caller.replace(/[()]/g, '')] ??
2265
+ getDynamicCallHandler(caller, objectType);
1521
2266
  if (handler) {
1522
2267
  return handler(caller, args, typeArgs);
1523
2268
  }
@@ -1525,17 +2270,17 @@ function getCallString(caller, args, typeArgs = '', objectType) {
1525
2270
  }
1526
2271
  function getOperatorText(operator) {
1527
2272
  switch (operator) {
1528
- case ts.SyntaxKind.PlusToken:
2273
+ case 'PlusToken':
1529
2274
  return '+';
1530
- case ts.SyntaxKind.MinusToken:
2275
+ case 'MinusToken':
1531
2276
  return '-';
1532
- case ts.SyntaxKind.TildeToken:
2277
+ case 'TildeToken':
1533
2278
  return '~';
1534
- case ts.SyntaxKind.ExclamationToken:
2279
+ case 'ExclamationToken':
1535
2280
  return '!';
1536
- case ts.SyntaxKind.PlusPlusToken:
2281
+ case 'PlusPlusToken':
1537
2282
  return '++';
1538
- case ts.SyntaxKind.MinusMinusToken:
2283
+ case 'MinusMinusToken':
1539
2284
  return '--';
1540
2285
  default:
1541
2286
  console.error('Did not find operator', operator);
@@ -1578,6 +2323,17 @@ function getTypeArguments(typeArguments) {
1578
2323
  return `[${args.join(', ')}]`;
1579
2324
  }
1580
2325
  function getParameterGoType(param) {
2326
+ // Rest parameter: ...args — use variadic syntax
2327
+ if (param.dotDotDotToken) {
2328
+ if (param.type) {
2329
+ let baseType = getType(param.type);
2330
+ // If annotated as T[], the variadic type is T
2331
+ if (baseType.startsWith('[]'))
2332
+ baseType = baseType.slice(2);
2333
+ return `...${baseType}`;
2334
+ }
2335
+ return '...interface{}';
2336
+ }
1581
2337
  if (param.type) {
1582
2338
  const explicitType = getType(param.type);
1583
2339
  return explicitType === ':' ? 'interface{}' : explicitType;
@@ -1640,12 +2396,12 @@ function getSafeName(name) {
1640
2396
  function getPromiseChannelType(node) {
1641
2397
  let parent = node.parent;
1642
2398
  while (parent) {
1643
- if (ts.isFunctionDeclaration(parent) ||
1644
- ts.isMethodDeclaration(parent) ||
1645
- ts.isFunctionExpression(parent)) {
2399
+ if (isFunctionDeclaration(parent) ||
2400
+ isMethodDeclaration(parent) ||
2401
+ isFunctionExpression(parent)) {
1646
2402
  if (parent.type &&
1647
- ts.isTypeReferenceNode(parent.type) &&
1648
- ts.isIdentifier(parent.type.typeName)) {
2403
+ isTypeReferenceNode(parent.type) &&
2404
+ isIdentifier(parent.type.typeName)) {
1649
2405
  if (parent.type.typeName.text === 'Promise' &&
1650
2406
  parent.type.typeArguments &&
1651
2407
  parent.type.typeArguments.length > 0) {
@@ -1660,7 +2416,7 @@ function getPromiseChannelType(node) {
1660
2416
  }
1661
2417
  function visitPromiseReturn(node, options) {
1662
2418
  const callback = node.arguments?.[0];
1663
- if (!callback || (!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback))) {
2419
+ if (!callback || (!isArrowFunction(callback) && !isFunctionExpression(callback))) {
1664
2420
  return `return ${visit(node)}` + (options.inline ? '' : ';\n\t');
1665
2421
  }
1666
2422
  const channelType = getPromiseChannelType(node);
@@ -1668,14 +2424,14 @@ function visitPromiseReturn(node, options) {
1668
2424
  const resolveName = resolveParam ? visit(resolveParam.name) : '';
1669
2425
  const prevResolveName = promiseResolveName;
1670
2426
  promiseResolveName = resolveName;
1671
- const body = ts.isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
2427
+ const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
1672
2428
  promiseResolveName = prevResolveName;
1673
2429
  return (`ch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch` +
1674
2430
  (options.inline ? '' : ';\n\t'));
1675
2431
  }
1676
2432
  function visitNewPromise(node) {
1677
2433
  const callback = node.arguments?.[0];
1678
- if (!callback || (!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback))) {
2434
+ if (!callback || (!isArrowFunction(callback) && !isFunctionExpression(callback))) {
1679
2435
  return 'NewPromise()';
1680
2436
  }
1681
2437
  const channelType = getPromiseChannelType(node);
@@ -1683,7 +2439,7 @@ function visitNewPromise(node) {
1683
2439
  const resolveName = resolveParam ? visit(resolveParam.name) : '';
1684
2440
  const prevResolveName = promiseResolveName;
1685
2441
  promiseResolveName = resolveName;
1686
- const body = ts.isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
2442
+ const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
1687
2443
  promiseResolveName = prevResolveName;
1688
2444
  return `func() chan ${channelType} {\n\t\tch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch;\n\t}()`;
1689
2445
  }
@@ -1706,19 +2462,19 @@ function extractMapValueType(mapType) {
1706
2462
  function visitNewMap(node) {
1707
2463
  let keyType = 'interface{}';
1708
2464
  let valueType = 'interface{}';
1709
- if (node.typeArguments && node.typeArguments.length === 2) {
1710
- keyType = getType(node.typeArguments[0]);
1711
- valueType = getType(node.typeArguments[1]);
2465
+ if ((node.typeArguments ?? []) && (node.typeArguments ?? []).length === 2) {
2466
+ keyType = getType((node.typeArguments ?? [])[0]);
2467
+ valueType = getType((node.typeArguments ?? [])[1]);
1712
2468
  }
1713
2469
  const mapType = `map[${keyType}]${valueType}`;
1714
- const args = node.arguments;
1715
- if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
2470
+ const args = (node.arguments ?? []);
2471
+ if (!args || args.length === 0 || !isArrayLiteralExpression(args[0])) {
1716
2472
  return `make(${mapType})`;
1717
2473
  }
1718
2474
  const initArg = args[0];
1719
2475
  const tmp = getTempName('map');
1720
2476
  const entries = initArg.elements
1721
- .filter((el) => ts.isArrayLiteralExpression(el) && el.elements.length >= 2)
2477
+ .filter((el) => isArrayLiteralExpression(el) && el.elements.length >= 2)
1722
2478
  .map((el) => {
1723
2479
  const pair = el;
1724
2480
  return `${tmp}[${visit(pair.elements[0])}] = ${visit(pair.elements[1])}`;
@@ -1728,12 +2484,12 @@ function visitNewMap(node) {
1728
2484
  }
1729
2485
  function visitNewSet(node) {
1730
2486
  let elementType = 'interface{}';
1731
- if (node.typeArguments && node.typeArguments.length === 1) {
1732
- elementType = getType(node.typeArguments[0]);
2487
+ if ((node.typeArguments ?? []) && (node.typeArguments ?? []).length === 1) {
2488
+ elementType = getType((node.typeArguments ?? [])[0]);
1733
2489
  }
1734
2490
  const setType = `map[${elementType}]struct{}`;
1735
- const args = node.arguments;
1736
- if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
2491
+ const args = (node.arguments ?? []);
2492
+ if (!args || args.length === 0 || !isArrayLiteralExpression(args[0])) {
1737
2493
  return `make(${setType})`;
1738
2494
  }
1739
2495
  const initArg = args[0];
@@ -1774,28 +2530,45 @@ function includeLocalImport(code, dir, goFileName) {
1774
2530
  }
1775
2531
  if (!goFileName) {
1776
2532
  // 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);
2533
+ const sf = parseFunction(code);
1778
2534
  for (const stmt of sf.statements) {
1779
2535
  visit(stmt, { addFunctionOutside: true });
1780
2536
  }
1781
2537
  currentFileDir = prevDir;
1782
2538
  return;
1783
2539
  }
1784
- // Separate-file mode (local TS imports): transpile to its own Go file
2540
+ // Separate-file mode (local TS imports): transpile to its own Go file.
2541
+ // Helper functions are NOT emitted per-file: every generated file shares
2542
+ // `package main`, so helpers are emitted once in the main output file.
1785
2543
  const savedOutsideNodes = outsideNodes;
1786
2544
  const savedPackages = [...importedPackages];
1787
2545
  outsideNodes = [];
1788
2546
  importedPackages.clear();
1789
- const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
2547
+ helperProvidedPackages.clear();
2548
+ const sf = parseFunction(code);
1790
2549
  const inlineLines = [];
1791
2550
  for (const stmt of sf.statements) {
1792
2551
  const result = visit(stmt, { addFunctionOutside: true });
1793
2552
  if (result.trim())
1794
2553
  inlineLines.push(result);
1795
2554
  }
1796
- const fileImports = [...importedPackages].map((pkg) => `import "${pkg}"`).join('\n');
1797
2555
  const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
1798
2556
  const fileInline = inlineLines.join('\n');
2557
+ // Computed after all visits: function bodies (outside nodes) register
2558
+ // packages too, and imports must be captured after those visits.
2559
+ // Helper-provided packages are only imported here if the file's own code
2560
+ // references them; the main file (which carries the helper sources)
2561
+ // always imports them.
2562
+ const fileCode = `${fileInline}\n${fileOutside}`;
2563
+ const fileImports = [...importedPackages]
2564
+ .filter((pkg) => {
2565
+ if (!helperProvidedPackages.has(pkg))
2566
+ return true;
2567
+ const name = pkg.split('/').pop();
2568
+ return fileCode.includes(`${name}.`);
2569
+ })
2570
+ .map((pkg) => `import "${pkg}"`)
2571
+ .join('\n');
1799
2572
  const parts = ['package main'];
1800
2573
  if (fileImports)
1801
2574
  parts.push(fileImports);
@@ -1804,11 +2577,14 @@ function includeLocalImport(code, dir, goFileName) {
1804
2577
  if (fileOutside.trim())
1805
2578
  parts.push(fileOutside.trim());
1806
2579
  localImportFiles.set(goFileName, parts.join('\n\n'));
1807
- // Restore main-file state
2580
+ // Restore main-file state; helper-provided packages carry over so the main
2581
+ // file (which carries the helper sources) imports them.
1808
2582
  outsideNodes = savedOutsideNodes;
1809
2583
  importedPackages.clear();
1810
2584
  for (const p of savedPackages)
1811
2585
  importedPackages.add(p);
2586
+ for (const p of helperProvidedPackages)
2587
+ importedPackages.add(p);
1812
2588
  currentFileDir = prevDir;
1813
2589
  }
1814
2590
  function registerGoPackageAliases(node, goPkg) {
@@ -1821,14 +2597,14 @@ function registerGoPackageAliases(node, goPkg) {
1821
2597
  importAliases.set(clause.name.text, pkgName);
1822
2598
  }
1823
2599
  if (clause.namedBindings) {
1824
- if (ts.isNamespaceImport(clause.namedBindings)) {
2600
+ if (isNamespaceImport(clause.namedBindings)) {
1825
2601
  // `import * as myFmt from 'go:fmt'`
1826
2602
  const localName = clause.namedBindings.name.text;
1827
2603
  if (localName !== pkgName) {
1828
2604
  importAliases.set(localName, pkgName);
1829
2605
  }
1830
2606
  }
1831
- else if (ts.isNamedImports(clause.namedBindings)) {
2607
+ else if (isNamedImports(clause.namedBindings)) {
1832
2608
  // `import { Println, Sprintf as Spf } from 'go:fmt'`
1833
2609
  for (const el of clause.namedBindings.elements) {
1834
2610
  if (el.isTypeOnly)
@@ -1847,7 +2623,7 @@ function getImportLocalName(node) {
1847
2623
  if (clause.name)
1848
2624
  return clause.name.text;
1849
2625
  if (clause.namedBindings) {
1850
- if (ts.isNamespaceImport(clause.namedBindings))
2626
+ if (isNamespaceImport(clause.namedBindings))
1851
2627
  return clause.namedBindings.name.text;
1852
2628
  }
1853
2629
  return null;
@@ -1856,34 +2632,375 @@ function getImportLocalName(node) {
1856
2632
  // For default/namespace imports (e.g. `import path from 'node:path'`), entries are registered
1857
2633
  // as `callHandlers[localName.funcName]`. For named imports (e.g. `import { join } from 'node:path'`),
1858
2634
  // the Go identifier is stored in importAliases so bare calls like `join(...)` resolve correctly.
2635
+ // Each emitter registers the Go packages it needs via `needPkg` only when actually used,
2636
+ // because Go rejects unused imports.
2637
+ function needPkg(pkg) {
2638
+ importedPackages.add(pkg);
2639
+ }
2640
+ // Marks a Go helper as used; also registers the packages the helper needs,
2641
+ // since the import list is serialized before helpers are emitted.
2642
+ function useHelper(id) {
2643
+ if (usedHelpers.has(id))
2644
+ return;
2645
+ usedHelpers.add(id);
2646
+ for (const pkg of helperPackages[id] ?? []) {
2647
+ importedPackages.add(pkg);
2648
+ helperProvidedPackages.add(pkg);
2649
+ }
2650
+ }
1859
2651
  const nodeModuleMappings = {
1860
2652
  path: {
1861
- goPackage: 'path/filepath',
1862
2653
  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: { ... } },
2654
+ join: (args) => {
2655
+ needPkg('path/filepath');
2656
+ return `filepath.Join(${args.join(', ')})`;
2657
+ },
2658
+ dirname: (args) => {
2659
+ needPkg('path/filepath');
2660
+ return `filepath.Dir(${args[0]})`;
2661
+ },
2662
+ basename: (args) => {
2663
+ needPkg('path/filepath');
2664
+ if (args[1]) {
2665
+ needPkg('strings');
2666
+ return `strings.TrimSuffix(filepath.Base(${args[0]}), ${args[1]})`;
2667
+ }
2668
+ return `filepath.Base(${args[0]})`;
2669
+ },
2670
+ extname: (args) => {
2671
+ needPkg('path/filepath');
2672
+ return `filepath.Ext(${args[0]})`;
2673
+ },
2674
+ resolve: (args) => {
2675
+ needPkg('path/filepath');
2676
+ return `func() string { p, _ := filepath.Abs(filepath.Join(${args.join(', ')})); return p }()`;
2677
+ }
2678
+ }
2679
+ },
2680
+ fs: {
2681
+ functions: {
2682
+ readFileSync: (args) => {
2683
+ useHelper('readFile');
2684
+ return `TnReadFile(${args[0]})`;
2685
+ },
2686
+ writeFileSync: (args) => {
2687
+ useHelper('writeFile');
2688
+ return `TnWriteFile(${args[0]}, ${args[1]})`;
2689
+ },
2690
+ appendFileSync: (args) => {
2691
+ useHelper('appendFile');
2692
+ return `TnAppendFile(${args[0]}, ${args[1]})`;
2693
+ },
2694
+ existsSync: (args) => {
2695
+ needPkg('os');
2696
+ return `func() bool { _, err := os.Stat(${args[0]}); return !os.IsNotExist(err) }()`;
2697
+ },
2698
+ mkdirSync: (args) => {
2699
+ useHelper('mkdirAll');
2700
+ return `TnMkdirAll(${args[0]})`;
2701
+ },
2702
+ readdirSync: (args) => {
2703
+ useHelper('readDir');
2704
+ return `TnReadDir(${args[0]})`;
2705
+ },
2706
+ copyFileSync: (args) => {
2707
+ useHelper('copyFile');
2708
+ return `TnCopyFile(${args[0]}, ${args[1]})`;
2709
+ },
2710
+ rmSync: (args) => {
2711
+ useHelper('removeAll');
2712
+ return `TnRemoveAll(${args[0]})`;
2713
+ }
2714
+ }
2715
+ },
2716
+ url: {
2717
+ functions: {
2718
+ fileURLToPath: (args) => {
2719
+ useHelper('fileURLToPath');
2720
+ return `TnFileURLToPath(${args[0]})`;
2721
+ },
2722
+ pathToFileURL: (args) => {
2723
+ useHelper('pathToFileURL');
2724
+ return `TnPathToFileURL(${args[0]})`;
2725
+ }
2726
+ }
2727
+ },
2728
+ os: {
2729
+ functions: {
2730
+ platform: () => {
2731
+ useHelper('osPlatform');
2732
+ return 'TnOsPlatform()';
2733
+ },
2734
+ homedir: () => {
2735
+ needPkg('os');
2736
+ return `func() string { h, _ := os.UserHomeDir(); return h }()`;
2737
+ },
2738
+ tmpdir: () => {
2739
+ needPkg('os');
2740
+ return 'os.TempDir()';
2741
+ }
2742
+ }
2743
+ },
2744
+ child_process: {
2745
+ functions: {
2746
+ // exec(command) — run through the shell, returns { stdout, stderr, status }
2747
+ exec: (args) => {
2748
+ useHelper('exec');
2749
+ return `TnExecShell(${args[0]})`;
2750
+ },
2751
+ // execSync(command) — run through the shell, returns stdout (panics on failure)
2752
+ execSync: (args) => {
2753
+ useHelper('exec');
2754
+ useHelper('execSync');
2755
+ return `TnExecShellSync(${args[0]})`;
2756
+ },
2757
+ // spawnSync(file, args, options) — matches Node's sync API.
2758
+ // options with `inherit` → run with the parent's stdio (returns exit code);
2759
+ // otherwise output is captured. Fields match Node: stdout/stderr/status.
2760
+ spawnSync: (args) => {
2761
+ useHelper('exec');
2762
+ useHelper('runInherit');
2763
+ if (args[2]?.includes('inherit')) {
2764
+ return `TnRunInherit(${args[0]})`;
2765
+ }
2766
+ // Untyped array literals visit as `[] {…}` — give them the []string
2767
+ // type the variadic helper needs (`[]string{…}...` is valid Go spread)
2768
+ const arrArg = args[1] ?? '[]string{}';
2769
+ const typedArg = arrArg.startsWith('[]') && !arrArg.startsWith('[]string')
2770
+ ? `[]string ${arrArg.slice(2)}`
2771
+ : arrArg;
2772
+ if (args[2]?.includes('input')) {
2773
+ // {input: expr, ...} → run with expr as stdin
2774
+ useHelper('execInput');
2775
+ const m = /input:\s*([^,}]+)/.exec(args[2]);
2776
+ const inputExpr = m ? m[1] : '""';
2777
+ return `TnExecInput(${args[0]}, ${inputExpr}, ${typedArg}...)`;
2778
+ }
2779
+ return `TnExec(${args[0]}, ${typedArg}...)`;
2780
+ }
2781
+ }
2782
+ },
2783
+ readline: {
2784
+ functions: {
2785
+ // question(prompt) — print prompt and read one line from stdin (synchronous)
2786
+ question: (args) => {
2787
+ useHelper('question');
2788
+ return `TnQuestion(${args[0] ?? '""'})`;
2789
+ }
2790
+ }
2791
+ }
1875
2792
  };
2793
+ // Go packages required by each helper, registered when the helper is used.
2794
+ const helperPackages = {
2795
+ readFile: ['os'],
2796
+ writeFile: ['os'],
2797
+ appendFile: ['os'],
2798
+ mkdirAll: ['os'],
2799
+ readDir: ['os'],
2800
+ copyFile: ['os', 'io', 'path/filepath'],
2801
+ removeAll: ['os'],
2802
+ fileURLToPath: ['net/url', 'path/filepath', 'strings'],
2803
+ pathToFileURL: ['net/url', 'path/filepath'],
2804
+ osPlatform: ['runtime'],
2805
+ exec: ['os/exec', 'bytes', 'runtime'],
2806
+ execSync: ['os/exec', 'bytes', 'runtime'],
2807
+ runInherit: ['os/exec', 'os'],
2808
+ question: ['bufio', 'fmt', 'os', 'strings'],
2809
+ getenv: ['os'],
2810
+ execInput: ['os/exec', 'bytes', 'strings']
2811
+ };
2812
+ // Go helper sources emitted (once) into the main output file when used.
2813
+ // Field names are intentionally lowercase: every generated file is `package main`,
2814
+ // so `result.stdout` in emitted code resolves to the struct field directly.
2815
+ const goHelpers = {
2816
+ readFile: `func TnReadFile(path string) string {
2817
+ data, err := os.ReadFile(path)
2818
+ if err != nil {
2819
+ panic(err)
2820
+ }
2821
+ return string(data)
2822
+ }`,
2823
+ writeFile: `func TnWriteFile(path string, content string) {
2824
+ if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
2825
+ panic(err)
2826
+ }
2827
+ }`,
2828
+ appendFile: `func TnAppendFile(path string, content string) {
2829
+ f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
2830
+ if err != nil {
2831
+ panic(err)
2832
+ }
2833
+ defer f.Close()
2834
+ if _, err := f.WriteString(content); err != nil {
2835
+ panic(err)
2836
+ }
2837
+ }`,
2838
+ mkdirAll: `func TnMkdirAll(path string) {
2839
+ if err := os.MkdirAll(path, 0o755); err != nil {
2840
+ panic(err)
2841
+ }
2842
+ }`,
2843
+ readDir: `func TnReadDir(path string) []string {
2844
+ entries, err := os.ReadDir(path)
2845
+ if err != nil {
2846
+ panic(err)
2847
+ }
2848
+ names := make([]string, 0, len(entries))
2849
+ for _, e := range entries {
2850
+ names = append(names, e.Name())
2851
+ }
2852
+ return names
2853
+ }`,
2854
+ copyFile: `func TnCopyFile(src string, dst string) {
2855
+ in, err := os.Open(src)
2856
+ if err != nil {
2857
+ panic(err)
2858
+ }
2859
+ defer in.Close()
2860
+ if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
2861
+ panic(err)
2862
+ }
2863
+ out, err := os.Create(dst)
2864
+ if err != nil {
2865
+ panic(err)
2866
+ }
2867
+ defer out.Close()
2868
+ if _, err := io.Copy(out, in); err != nil {
2869
+ panic(err)
2870
+ }
2871
+ }`,
2872
+ removeAll: `func TnRemoveAll(path string) {
2873
+ if err := os.RemoveAll(path); err != nil {
2874
+ panic(err)
2875
+ }
2876
+ }`,
2877
+ fileURLToPath: `func TnFileURLToPath(u string) string {
2878
+ trimmed := strings.TrimPrefix(u, "file://")
2879
+ if unescaped, err := url.PathUnescape(trimmed); err == nil {
2880
+ trimmed = unescaped
2881
+ }
2882
+ return filepath.FromSlash(trimmed)
2883
+ }`,
2884
+ pathToFileURL: `func TnPathToFileURL(path string) string {
2885
+ abs, err := filepath.Abs(path)
2886
+ if err != nil {
2887
+ panic(err)
2888
+ }
2889
+ return "file://" + url.PathEscape(filepath.ToSlash(abs))
2890
+ }`,
2891
+ osPlatform: `func TnOsPlatform() string {
2892
+ if runtime.GOOS == "windows" {
2893
+ return "win32"
2894
+ }
2895
+ if runtime.GOOS == "darwin" {
2896
+ return "darwin"
2897
+ }
2898
+ return runtime.GOOS
2899
+ }`,
2900
+ exec: `type tnExecResult struct {
2901
+ stdout string
2902
+ stderr string
2903
+ status float64
2904
+ }
2905
+
2906
+ func TnExec(name string, args ...string) tnExecResult {
2907
+ cmd := exec.Command(name, args...)
2908
+ var stdout, stderr bytes.Buffer
2909
+ cmd.Stdout = &stdout
2910
+ cmd.Stderr = &stderr
2911
+ err := cmd.Run()
2912
+ status := 0.0
2913
+ if err != nil {
2914
+ if exitErr, ok := err.(*exec.ExitError); ok {
2915
+ status = float64(exitErr.ExitCode())
2916
+ } else {
2917
+ panic(err)
2918
+ }
2919
+ }
2920
+ return tnExecResult{stdout: stdout.String(), stderr: stderr.String(), status: status}
2921
+ }
2922
+
2923
+ func TnExecShell(command string) tnExecResult {
2924
+ if runtime.GOOS == "windows" {
2925
+ return TnExec("cmd", "/C", command)
2926
+ }
2927
+ return TnExec("sh", "-c", command)
2928
+ }`,
2929
+ execSync: `func TnExecShellSync(command string) string {
2930
+ result := TnExecShell(command)
2931
+ if result.status != 0 {
2932
+ panic(result.stderr)
2933
+ }
2934
+ return result.stdout
2935
+ }`,
2936
+ getenv: `func TnGetenv(name string) string {
2937
+ return os.Getenv(name)
2938
+ }`,
2939
+ execInput: `func TnExecInput(name string, input string, args ...string) tnExecResult {
2940
+ cmd := exec.Command(name, args...)
2941
+ cmd.Stdin = strings.NewReader(input)
2942
+ var stdout, stderr bytes.Buffer
2943
+ cmd.Stdout = &stdout
2944
+ cmd.Stderr = &stderr
2945
+ err := cmd.Run()
2946
+ status := 0.0
2947
+ if err != nil {
2948
+ if exitErr, ok := err.(*exec.ExitError); ok {
2949
+ status = float64(exitErr.ExitCode())
2950
+ } else {
2951
+ panic(err)
2952
+ }
2953
+ }
2954
+ return tnExecResult{stdout: stdout.String(), stderr: stderr.String(), status: status}
2955
+ }`,
2956
+ runInherit: `func TnRunInherit(name string, args ...string) float64 {
2957
+ cmd := exec.Command(name, args...)
2958
+ cmd.Stdin = os.Stdin
2959
+ cmd.Stdout = os.Stdout
2960
+ cmd.Stderr = os.Stderr
2961
+ err := cmd.Run()
2962
+ if err != nil {
2963
+ if exitErr, ok := err.(*exec.ExitError); ok {
2964
+ return float64(exitErr.ExitCode())
2965
+ }
2966
+ panic(err)
2967
+ }
2968
+ return 0
2969
+ }`,
2970
+ question: `var tnStdinReader = bufio.NewReader(os.Stdin)
2971
+
2972
+ func TnQuestion(prompt string) string {
2973
+ if prompt != "" {
2974
+ fmt.Print(prompt)
2975
+ }
2976
+ // Reuse one buffered reader: a fresh reader per call would discard
2977
+ // buffered stdin and hit EOF on the second question.
2978
+ line, err := tnStdinReader.ReadString('\\n')
2979
+ if err != nil && line == "" {
2980
+ panic(err)
2981
+ }
2982
+ return strings.TrimRight(line, "\\r\\n")
2983
+ }`
2984
+ };
2985
+ // Returns the Go source for all helpers used in this transpilation, or '' if none.
2986
+ // Emitted only into the main output file: every generated file shares `package main`,
2987
+ // so helpers are visible across files and must not be duplicated.
2988
+ function emitGoHelpers() {
2989
+ return [...usedHelpers]
2990
+ .map((id) => goHelpers[id])
2991
+ .filter(Boolean)
2992
+ .join('\n\n');
2993
+ }
1876
2994
  // Mapping from Node.js stdlib module names to Go setup functions.
1877
2995
  // Each entry adds the required Go imports and registers call handlers for the local identifier.
1878
2996
  function setupNodeModuleImport(node, nodeModule) {
1879
2997
  const mapping = nodeModuleMappings[nodeModule];
1880
2998
  if (!mapping)
1881
2999
  return;
1882
- importedPackages.add(mapping.goPackage);
1883
3000
  const clause = node.importClause;
1884
3001
  if (!clause)
1885
3002
  return;
1886
- if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
3003
+ if (clause.namedBindings && isNamedImports(clause.namedBindings)) {
1887
3004
  // Named imports: `import { join, dirname } from 'node:path'`
1888
3005
  // Map each local name directly to its Go qualified function via importAliases,
1889
3006
  // so bare calls like `join(...)` resolve to `filepath.Join(...)`.
@@ -1909,7 +3026,7 @@ function setupNodeModuleImport(node, nodeModule) {
1909
3026
  }
1910
3027
  }
1911
3028
  function visitImportDeclaration(node) {
1912
- if (!ts.isStringLiteral(node.moduleSpecifier))
3029
+ if (!isStringLiteral(node.moduleSpecifier))
1913
3030
  return '';
1914
3031
  const moduleSpec = node.moduleSpecifier.text;
1915
3032
  // Relative imports → transpile to a separate Go file
@@ -1952,16 +3069,27 @@ function visitImportDeclaration(node) {
1952
3069
  includeLocalImport(resolved.content, resolved.dir, goFileName);
1953
3070
  }
1954
3071
  }
3072
+ // Register default/namespace import name as a "stripped" namespace
3073
+ // e.g. `import ts from 'typescript'` → ts.createSourceFile → createSourceFile
3074
+ if (node.importClause) {
3075
+ const clause = node.importClause;
3076
+ if (clause.name) {
3077
+ defaultImportNamespaces.add(clause.name.text);
3078
+ }
3079
+ else if (clause.namedBindings && isNamespaceImport(clause.namedBindings)) {
3080
+ defaultImportNamespaces.add(clause.namedBindings.name.text);
3081
+ }
3082
+ }
1955
3083
  return '';
1956
3084
  }
1957
3085
  function getForOfVarNames(initializer) {
1958
- if (!ts.isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
3086
+ if (!isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
1959
3087
  return ['_'];
1960
3088
  }
1961
3089
  const decl = initializer.declarations[0];
1962
- if (ts.isArrayBindingPattern(decl.name)) {
3090
+ if (isArrayBindingPattern(decl.name)) {
1963
3091
  return decl.name.elements.map((el) => {
1964
- if (ts.isOmittedExpression(el))
3092
+ if (isOmittedExpression(el))
1965
3093
  return '_';
1966
3094
  return visit(el.name);
1967
3095
  });
@@ -1972,17 +3100,17 @@ function visitTryStatement(node, options) {
1972
3100
  const deferreds = [];
1973
3101
  // Register finally first (LIFO: runs last after catch)
1974
3102
  if (node.finallyBlock) {
1975
- const finallyBody = node.finallyBlock.statements.map((s) => visit(s)).join('\t');
3103
+ const finallyBody = (node.finallyBlock.statements ?? []).map((s) => visit(s)).join('\t');
1976
3104
  deferreds.push(`defer func() {\n\t\t\t${finallyBody}\t\t\t}()`);
1977
3105
  }
1978
3106
  // Register catch second (LIFO: runs first, handles panic via recover)
1979
3107
  if (node.catchClause) {
1980
3108
  const varDecl = node.catchClause.variableDeclaration;
1981
3109
  const catchVar = varDecl ? visit(varDecl.name) : '_r';
1982
- const catchBody = node.catchClause.block.statements.map((s) => visit(s)).join('\t');
3110
+ const catchBody = (node.catchClause.block.statements ?? []).map((s) => visit(s)).join('\t');
1983
3111
  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
3112
  }
1985
- const tryBody = node.tryBlock.statements.map((s) => visit(s)).join('\t');
3113
+ const tryBody = (node.tryBlock.statements ?? []).map((s) => visit(s)).join('\t');
1986
3114
  const body = [...deferreds, tryBody].join('\n\t\t\t');
1987
3115
  return `func() {\n\t\t\t${body}\n\t\t\t}()` + (options.inline ? '' : ';\n\t');
1988
3116
  }