typenative 0.0.20 → 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,7 +1,255 @@
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();
@@ -43,14 +291,108 @@ let currentFileDir = null;
43
291
  const includedLocalImports = new Set();
44
292
  // Collects Go source files generated from local TS imports (filename → content)
45
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
+ }
46
381
  // Default import namespaces from npm/local packages (e.g. `import ts from 'typescript'` → 'ts')
47
382
  // Property accesses on these are stripped: ts.createSourceFile → createSourceFile
48
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();
49
391
  export function transpileToNative(code, options) {
50
392
  fileResolver = options?.readFile ?? null;
393
+ parseFunction = options?.parse ?? defaultParseFunction;
51
394
  currentFileDir = null;
52
- const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
53
- TypeCheker = ts.createProgram(['main.ts'], {}).getTypeChecker();
395
+ const sourceFile = parseFunction(code);
54
396
  importedPackages.clear();
55
397
  outsideNodes = [];
56
398
  classNames.clear();
@@ -71,6 +413,8 @@ export function transpileToNative(code, options) {
71
413
  defaultImportNamespaces.clear();
72
414
  classStaticMethods.clear();
73
415
  classStaticProps.clear();
416
+ usedHelpers.clear();
417
+ helperProvidedPackages.clear();
74
418
  const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
75
419
  const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
76
420
  const main = `package main
@@ -81,18 +425,19 @@ func main() {
81
425
  ${transpiledCode.trim()}
82
426
  }
83
427
 
84
- ${transpiledCodeOutside.trim()}`;
428
+ ${transpiledCodeOutside.trim()}
429
+ ${emitGoHelpers()}`.trimEnd();
85
430
  return { main, files: localImportFiles };
86
431
  }
87
432
  export function visit(node, options = {}) {
88
433
  let code = '';
89
- if (ts.isSourceFile(node)) {
90
- return node.statements
434
+ if (isSourceFile(node)) {
435
+ return (node.statements ?? [])
91
436
  .map((n) => visit(n, { addFunctionOutside: true }))
92
437
  .filter((n) => !!n)
93
438
  .join(options.inline ? '' : '\n\t');
94
439
  }
95
- else if (ts.isIdentifier(node)) {
440
+ else if (isIdentifier(node)) {
96
441
  if (node.text === 'undefined')
97
442
  return 'nil';
98
443
  const goAlias = importAliases.get(node.text);
@@ -100,31 +445,31 @@ export function visit(node, options = {}) {
100
445
  return goAlias;
101
446
  return getSafeName(node.text);
102
447
  }
103
- else if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
448
+ else if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
104
449
  return toGoStringLiteral(node.text);
105
450
  }
106
- else if (ts.isAsExpression(node)) {
451
+ else if (isAsExpression(node)) {
107
452
  return visit(node.expression);
108
453
  }
109
- else if (ts.isTypeAssertionExpression(node)) {
454
+ else if (isTypeAssertionExpression(node)) {
110
455
  return visit(node.expression);
111
456
  }
112
- else if (ts.isTemplateExpression(node)) {
457
+ else if (isTemplateExpression(node)) {
113
458
  return visitTemplateExpression(node);
114
459
  }
115
- else if (ts.isNumericLiteral(node)) {
460
+ else if (isNumericLiteral(node)) {
116
461
  return `float64(${node.text})`;
117
462
  }
118
- else if (ts.isToken(node) && node.kind === ts.SyntaxKind.TrueKeyword) {
463
+ else if (isToken(node) && node.kind === 'TrueKeyword') {
119
464
  return `true`;
120
465
  }
121
- else if (ts.isToken(node) && node.kind === ts.SyntaxKind.FalseKeyword) {
466
+ else if (isToken(node) && node.kind === 'FalseKeyword') {
122
467
  return `false`;
123
468
  }
124
- else if (ts.isToken(node) && node.kind === ts.SyntaxKind.NullKeyword) {
469
+ else if (isToken(node) && node.kind === 'NullKeyword') {
125
470
  return `nil`;
126
471
  }
127
- else if (ts.isRegularExpressionLiteral(node)) {
472
+ else if (isRegularExpressionLiteral(node)) {
128
473
  importedPackages.add('regexp');
129
474
  const text = node.text; // e.g. /pattern/flags
130
475
  const lastSlash = text.lastIndexOf('/');
@@ -134,38 +479,38 @@ export function visit(node, options = {}) {
134
479
  const escaped = pattern.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
135
480
  return `regexp.MustCompile("${goFlags}${escaped}")`;
136
481
  }
137
- else if (ts.isArrayLiteralExpression(node)) {
138
- const type = ts.isVariableDeclaration(node.parent) ? getType(node.parent.type, true) : '';
139
- const hasSpread = node.elements.some((e) => ts.isSpreadElement(e));
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));
140
485
  if (hasSpread) {
141
486
  return visitSpreadArrayLiteral(node, type);
142
487
  }
143
- return `[]${type} {${node.elements.map((e) => visit(e)).join(', ')}}`;
488
+ return `[]${type} {${(node.elements ?? []).map((e) => visit(e)).join(', ')}}`;
144
489
  }
145
- else if (ts.isBlock(node)) {
146
- return `{\n\t\t${options.prefixBlockContent ?? ''}${node.statements
490
+ else if (isBlock(node)) {
491
+ return `{\n\t\t${options.prefixBlockContent ?? ''}${(node.statements ?? [])
147
492
  .map((n) => visit(n))
148
493
  .join('\t')}${options.extraBlockContent ?? ''}}${options.inline ? '' : '\n\t'}`;
149
494
  }
150
- else if (ts.isElementAccessExpression(node)) {
495
+ else if (isElementAccessExpression(node)) {
151
496
  if (hasQuestionDot(node)) {
152
497
  return visitOptionalElementAccess(node);
153
498
  }
154
499
  return `${visit(node.expression)}[int(${visit(node.argumentExpression)})]`;
155
500
  }
156
- else if (ts.isPropertyAccessExpression(node)) {
501
+ else if (isPropertyAccessExpression(node)) {
157
502
  if (hasQuestionDot(node)) {
158
503
  return visitOptionalPropertyAccess(node);
159
504
  }
160
- if (ts.isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
505
+ if (isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
161
506
  return `${getSafeName(node.expression.text)}_${getEnumMemberName(node.name)}`;
162
507
  }
163
508
  // Strip default import namespace: `ts.createSourceFile` → `createSourceFile`
164
- if (ts.isIdentifier(node.expression) && defaultImportNamespaces.has(node.expression.text)) {
509
+ if (isIdentifier(node.expression) && defaultImportNamespaces.has(node.expression.text)) {
165
510
  return visit(node.name);
166
511
  }
167
512
  // Static member access: `Counter.count` → `Counter_count`
168
- if (ts.isIdentifier(node.expression)) {
513
+ if (isIdentifier(node.expression)) {
169
514
  const key = `${node.expression.text}.${node.name.text}`;
170
515
  if (classStaticMethods.has(key) || classStaticProps.has(key)) {
171
516
  return `${node.expression.text}_${node.name.text}`;
@@ -176,9 +521,9 @@ export function visit(node, options = {}) {
176
521
  const objectType = resolveExpressionType(node.expression);
177
522
  return getAcessString(leftSide, rightSide, objectType);
178
523
  }
179
- else if (ts.isVariableDeclaration(node)) {
524
+ else if (isVariableDeclaration(node)) {
180
525
  // Object destructuring: const { x, y } = obj
181
- if (ts.isObjectBindingPattern(node.name) && node.initializer) {
526
+ if (isObjectBindingPattern(node.name) && node.initializer) {
182
527
  const initExpr = visit(node.initializer);
183
528
  const parts = node.name.elements.map((el) => {
184
529
  const localName = visit(el.name);
@@ -192,12 +537,12 @@ export function visit(node, options = {}) {
192
537
  return parts.join(';\n\t');
193
538
  }
194
539
  // Array destructuring: const [a, b] = arr
195
- if (ts.isArrayBindingPattern(node.name) && node.initializer) {
540
+ if (isArrayBindingPattern(node.name) && node.initializer) {
196
541
  const initExpr = visit(node.initializer);
197
542
  const tmpVar = getTempName('arr');
198
543
  const parts = [`${tmpVar} := ${initExpr}`];
199
544
  node.name.elements.forEach((el, idx) => {
200
- if (ts.isOmittedExpression(el))
545
+ if (isOmittedExpression(el))
201
546
  return;
202
547
  const bindEl = el;
203
548
  const localName = visit(bindEl.name);
@@ -213,7 +558,7 @@ export function visit(node, options = {}) {
213
558
  }
214
559
  const type = getType(node.type);
215
560
  // Track variable type for type-aware method dispatch
216
- if (ts.isIdentifier(node.name)) {
561
+ if (isIdentifier(node.name)) {
217
562
  if (node.type) {
218
563
  variableGoTypes.set(node.name.text, getType(node.type));
219
564
  }
@@ -228,8 +573,8 @@ export function visit(node, options = {}) {
228
573
  }
229
574
  }
230
575
  else if (node.initializer &&
231
- ts.isNewExpression(node.initializer) &&
232
- ts.isIdentifier(node.initializer.expression)) {
576
+ isNewExpression(node.initializer) &&
577
+ isIdentifier(node.initializer.expression)) {
233
578
  if (node.initializer.expression.text === 'RegExp') {
234
579
  variableTypes.set(node.name.text, 'RegExp');
235
580
  }
@@ -238,7 +583,7 @@ export function visit(node, options = {}) {
238
583
  variableClassNames.set(node.name.text, node.initializer.expression.text);
239
584
  }
240
585
  }
241
- else if (node.initializer && ts.isRegularExpressionLiteral(node.initializer)) {
586
+ else if (node.initializer && isRegularExpressionLiteral(node.initializer)) {
242
587
  variableTypes.set(node.name.text, 'RegExp');
243
588
  }
244
589
  if (!variableGoTypes.has(node.name.text) && node.initializer) {
@@ -260,30 +605,30 @@ export function visit(node, options = {}) {
260
605
  }
261
606
  return `${type === ':' ? '' : 'var '}${visit(node.name)} ${type}${type === ':' ? '' : ' '}${initializer}`;
262
607
  }
263
- else if (ts.isCallExpression(node)) {
608
+ else if (isCallExpression(node)) {
264
609
  if (hasQuestionDot(node) ||
265
- (ts.isPropertyAccessExpression(node.expression) && hasQuestionDot(node.expression))) {
610
+ (isPropertyAccessExpression(node.expression) && hasQuestionDot(node.expression))) {
266
611
  return visitOptionalCall(node);
267
612
  }
268
613
  // IIFE with named function expression: (function name() { ... })()
269
- if (ts.isParenthesizedExpression(node.expression) &&
270
- ts.isFunctionExpression(node.expression.expression)) {
614
+ if (isParenthesizedExpression(node.expression) &&
615
+ isFunctionExpression(node.expression.expression)) {
271
616
  const fn = node.expression.expression;
272
- const parameterInfo = getFunctionParametersInfo(fn.parameters);
273
- if (fn.body && ts.isBlock(fn.body)) {
617
+ const parameterInfo = getFunctionParametersInfo(fn.parameters ?? []);
618
+ if (fn.body && isBlock(fn.body)) {
274
619
  prescanVariableDeclarations(fn.body);
275
620
  }
276
621
  const inferredRetType = inferFunctionBodyReturnType(fn);
277
622
  const returnType = inferredRetType ? ` ${inferredRetType}` : '';
278
- const args = node.arguments.map((a) => visit(a)).join(', ');
623
+ const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
279
624
  return `func(${parameterInfo.signature})${returnType} ${visit(fn.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}(${args})`;
280
625
  }
281
626
  // Handle setTimeout specially to get raw delay value
282
- if (ts.isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
627
+ if (isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
283
628
  importedPackages.add('time');
284
- const callback = visit(node.arguments[0]);
285
- const delayNode = node.arguments[1];
286
- 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);
287
632
  return `time.AfterFunc(${delay} * time.Millisecond, ${callback.trimEnd()})`;
288
633
  }
289
634
  const arrayHigherOrderCall = visitArrayHigherOrderCall(node);
@@ -292,69 +637,79 @@ export function visit(node, options = {}) {
292
637
  }
293
638
  const caller = visit(node.expression);
294
639
  const safeCaller = getSafeName(caller);
295
- const typeArgs = getTypeArguments(node.typeArguments);
640
+ const typeArgs = getTypeArguments((node.typeArguments ?? []));
296
641
  // Handle spread arguments: fn(...arr) → fn(arr...)
297
- const hasSpreadArg = node.arguments.some((a) => ts.isSpreadElement(a));
642
+ const hasSpreadArg = (node.arguments ?? []).some((a) => isSpreadElement(a));
298
643
  const args = hasSpreadArg
299
- ? node.arguments.map((a) => ts.isSpreadElement(a) ? `${visit(a.expression)}...` : visit(a))
300
- : node.arguments.map((a) => visit(a));
644
+ ? (node.arguments ?? []).map((a) => isSpreadElement(a) ? `${visit(a.expression)}...` : visit(a))
645
+ : (node.arguments ?? []).map((a) => visit(a));
301
646
  // Resolve object type for type-aware method dispatch
302
647
  let objectType;
303
- if (ts.isPropertyAccessExpression(node.expression)) {
648
+ if (isPropertyAccessExpression(node.expression)) {
304
649
  objectType = resolveExpressionType(node.expression.expression);
305
650
  }
306
651
  return getCallString(safeCaller, args, typeArgs, objectType);
307
652
  }
308
- else if (ts.isPrefixUnaryExpression(node)) {
653
+ else if (isPrefixUnaryExpression(node)) {
309
654
  return `${getOperatorText(node.operator)}${visit(node.operand)}`;
310
655
  }
311
- else if (ts.isPostfixUnaryExpression(node)) {
656
+ else if (isPostfixUnaryExpression(node)) {
312
657
  return `${visit(node.operand, { inline: true })}${getOperatorText(node.operator)}`;
313
658
  }
314
- else if (ts.isConditionalExpression(node)) {
659
+ else if (isConditionalExpression(node)) {
315
660
  return visitConditionalExpression(node);
316
661
  }
317
- else if (ts.isBinaryExpression(node)) {
318
- if (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
662
+ else if (isBinaryExpression(node)) {
663
+ if (node.operatorToken.kind === 'QuestionQuestionToken') {
319
664
  return visitNullishCoalescingExpression(node);
320
665
  }
321
- let op = node.operatorToken.getText();
666
+ let op = operatorTokenText(node.operatorToken);
322
667
  if (op === '===')
323
668
  op = '==';
324
669
  if (op === '!==')
325
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
+ }
326
681
  return `${visit(node.left)} ${op} ${visit(node.right)}`;
327
682
  }
328
- else if (ts.isParenthesizedExpression(node)) {
683
+ else if (isParenthesizedExpression(node)) {
329
684
  return `(${visit(node.expression)})`;
330
685
  }
331
- else if (ts.isAwaitExpression(node)) {
686
+ else if (isAwaitExpression(node)) {
332
687
  return `<-${visit(node.expression)}`;
333
688
  }
334
- else if (ts.isVariableDeclarationList(node)) {
335
- 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') +
336
691
  (options.inline ? '' : ';\n\t'));
337
692
  }
338
- else if (ts.isExpressionStatement(node)) {
693
+ else if (isExpressionStatement(node)) {
339
694
  return visit(node.expression) + (options.inline ? '' : ';\n\t');
340
695
  }
341
- else if (ts.isForStatement(node)) {
696
+ else if (isForStatement(node)) {
342
697
  return `for ${visit(node.initializer, { inline: true })}; ${visit(node.condition, {
343
698
  inline: true
344
699
  })}; ${visit(node.incrementor, { inline: true })}${visit(node.statement)}`;
345
700
  }
346
- else if (ts.isForInStatement(node)) {
347
- const varName = ts.isVariableDeclarationList(node.initializer)
701
+ else if (isForInStatement(node)) {
702
+ const varName = isVariableDeclarationList(node.initializer)
348
703
  ? visit(node.initializer.declarations[0].name)
349
704
  : visit(node.initializer);
350
705
  return `for ${varName} := range ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
351
706
  }
352
- else if (ts.isForOfStatement(node)) {
707
+ else if (isForOfStatement(node)) {
353
708
  // Unwrap Object.entries(x) → treat x as the iterable
354
709
  let iterNode = node.expression;
355
- if (ts.isCallExpression(iterNode) &&
356
- ts.isPropertyAccessExpression(iterNode.expression) &&
357
- ts.isIdentifier(iterNode.expression.expression) &&
710
+ if (isCallExpression(iterNode) &&
711
+ isPropertyAccessExpression(iterNode.expression) &&
712
+ isIdentifier(iterNode.expression.expression) &&
358
713
  iterNode.expression.expression.text === 'Object' &&
359
714
  iterNode.expression.name.text === 'entries' &&
360
715
  iterNode.arguments.length > 0) {
@@ -378,50 +733,62 @@ export function visit(node, options = {}) {
378
733
  }
379
734
  return `for _,${visit(node.initializer, { inline: true })}= range ${iterExpr}${visit(node.statement)}`;
380
735
  }
381
- else if (ts.isWhileStatement(node)) {
736
+ else if (isWhileStatement(node)) {
382
737
  return `for ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
383
738
  }
384
- else if (ts.isDoStatement(node)) {
739
+ else if (isDoStatement(node)) {
385
740
  const condition = `\tif !(${visit(node.expression, {
386
741
  inline: true
387
742
  })}) {\n\t\t\tbreak \n\t\t}\n\t`;
388
743
  return `for ${visit(node.statement, { inline: true, extraBlockContent: condition })}`;
389
744
  }
390
- else if (ts.isIfStatement(node)) {
391
- const condition = `if ${visit(node.expression, { inline: true })} ${visit(node.thenStatement, {
392
- inline: !!node.elseStatement
393
- })}`;
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}`;
394
754
  if (node.elseStatement) {
395
- 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}`;
396
763
  }
397
764
  return condition;
398
765
  }
399
- else if (ts.isSwitchStatement(node)) {
766
+ else if (isSwitchStatement(node)) {
400
767
  return `switch ${visit(node.expression)} ${visit(node.caseBlock)}`;
401
768
  }
402
- else if (ts.isCaseBlock(node)) {
403
- 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}`;
404
771
  }
405
- else if (ts.isCaseClause(node)) {
406
- const isFallThrough = !node.statements.some((c) => ts.isBreakStatement(c));
407
- return `case ${visit(node.expression, { inline: true })}: \n\t\t\t${node.statements
408
- .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))
409
776
  .map((s) => visit(s))
410
777
  .join('')}${isFallThrough ? 'fallthrough\n\t' : ''}`;
411
778
  }
412
- else if (ts.isDefaultClause(node)) {
413
- return `default: \n\t\t\t${node.statements
414
- .filter((n) => !ts.isBreakStatement(n))
779
+ else if (isDefaultClause(node)) {
780
+ return `default: \n\t\t\t${(node.statements ?? [])
781
+ .filter((n) => !isBreakStatement(n))
415
782
  .map((s) => visit(s))
416
783
  .join('')}`;
417
784
  }
418
- else if (ts.isBreakStatement(node)) {
785
+ else if (isBreakStatement(node)) {
419
786
  return 'break';
420
787
  }
421
- else if (ts.isThrowStatement(node)) {
788
+ else if (isThrowStatement(node)) {
422
789
  const expr = node.expression;
423
- if (ts.isNewExpression(expr) &&
424
- ts.isIdentifier(expr.expression) &&
790
+ if (isNewExpression(expr) &&
791
+ isIdentifier(expr.expression) &&
425
792
  expr.expression.text === 'Error') {
426
793
  const args = expr.arguments ?? [];
427
794
  const msg = args.length > 0 ? visit(args[0]) : '""';
@@ -429,27 +796,27 @@ export function visit(node, options = {}) {
429
796
  }
430
797
  return `panic(${visit(expr)})` + (options.inline ? '' : ';\n\t');
431
798
  }
432
- else if (ts.isTryStatement(node)) {
799
+ else if (isTryStatement(node)) {
433
800
  return visitTryStatement(node, options);
434
801
  }
435
- else if (ts.isReturnStatement(node)) {
802
+ else if (isReturnStatement(node)) {
436
803
  // Handle return new Promise(...)
437
804
  if (node.expression &&
438
- ts.isNewExpression(node.expression) &&
439
- ts.isIdentifier(node.expression.expression) &&
805
+ isNewExpression(node.expression) &&
806
+ isIdentifier(node.expression.expression) &&
440
807
  node.expression.expression.text === 'Promise') {
441
808
  return visitPromiseReturn(node.expression, options);
442
809
  }
443
810
  return (`return ${node.expression ? visit(node.expression) : ''}` + (options.inline ? '' : ';\n\t'));
444
811
  }
445
- else if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
812
+ else if (isFunctionDeclaration(node) || isFunctionExpression(node)) {
446
813
  if (options.addFunctionOutside) {
447
814
  outsideNodes.push(node);
448
815
  return '';
449
816
  }
450
817
  const typeParams = getTypeParameters(node.typeParameters);
451
- const parameterInfo = getFunctionParametersInfo(node.parameters);
452
- if (node.body && ts.isBlock(node.body)) {
818
+ const parameterInfo = getFunctionParametersInfo((node.parameters ?? []));
819
+ if (node.body && isBlock(node.body)) {
453
820
  prescanVariableDeclarations(node.body);
454
821
  }
455
822
  const inferredRetType = inferFunctionBodyReturnType(node);
@@ -472,24 +839,24 @@ export function visit(node, options = {}) {
472
839
  prefixBlockContent: parameterInfo.prefixBlockContent
473
840
  })}`;
474
841
  }
475
- else if (ts.isArrowFunction(node)) {
476
- const parameterInfo = getFunctionParametersInfo(node.parameters);
842
+ else if (isArrowFunction(node)) {
843
+ const parameterInfo = getFunctionParametersInfo((node.parameters ?? []));
477
844
  const inferredRetType = inferFunctionBodyReturnType(node);
478
845
  const returnType = inferredRetType ? ` ${inferredRetType}` : '';
479
- if (parameterInfo.prefixBlockContent && !ts.isBlock(node.body)) {
846
+ if (parameterInfo.prefixBlockContent && !isBlock(node.body)) {
480
847
  return `func(${parameterInfo.signature})${returnType} {\n\t\t${parameterInfo.prefixBlockContent}return ${visit(node.body)};\n\t}`;
481
848
  }
482
- if (!ts.isBlock(node.body)) {
849
+ if (!isBlock(node.body)) {
483
850
  return `func(${parameterInfo.signature})${returnType} { return ${visit(node.body)}; }`;
484
851
  }
485
852
  return `func(${parameterInfo.signature})${returnType} ${visit(node.body, {
486
853
  prefixBlockContent: parameterInfo.prefixBlockContent
487
854
  })}`;
488
855
  }
489
- else if (node.kind === ts.SyntaxKind.ThisKeyword) {
856
+ else if (node.kind === 'ThisKeyword') {
490
857
  return 'self';
491
858
  }
492
- else if (ts.isEnumDeclaration(node)) {
859
+ else if (isEnumDeclaration(node)) {
493
860
  const enumName = node.name.text;
494
861
  enumNames.add(enumName);
495
862
  enumBaseTypes.set(enumName, getEnumBaseType(node));
@@ -499,16 +866,16 @@ export function visit(node, options = {}) {
499
866
  }
500
867
  return visitEnumDeclaration(node);
501
868
  }
502
- else if (ts.isTypeAliasDeclaration(node)) {
869
+ else if (isTypeAliasDeclaration(node)) {
503
870
  typeAliases.set(node.name.text, node.type);
504
871
  return '';
505
872
  }
506
- else if (ts.isInterfaceDeclaration(node)) {
873
+ else if (isInterfaceDeclaration(node)) {
507
874
  if (options.addFunctionOutside) {
508
875
  outsideNodes.push(node);
509
876
  const properties = new Map();
510
- for (const member of node.members) {
511
- if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
877
+ for (const member of (node.members ?? [])) {
878
+ if (isPropertySignature(member) && isIdentifier(member.name)) {
512
879
  properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
513
880
  }
514
881
  }
@@ -520,10 +887,10 @@ export function visit(node, options = {}) {
520
887
  const name = visit(node.name);
521
888
  const typeParams = getTypeParameters(node.typeParameters);
522
889
  const extendedInterfaces = [];
523
- if (node.heritageClauses) {
524
- for (const clause of node.heritageClauses) {
525
- if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
526
- 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 ?? [])) {
527
894
  extendedInterfaces.push(visit(type.expression));
528
895
  }
529
896
  }
@@ -531,16 +898,16 @@ export function visit(node, options = {}) {
531
898
  }
532
899
  const methods = [];
533
900
  const properties = [];
534
- for (const member of node.members) {
535
- if (ts.isMethodSignature(member)) {
901
+ for (const member of (node.members ?? [])) {
902
+ if (isMethodSignature(member)) {
536
903
  const methodName = visit(member.name);
537
- const params = member.parameters
904
+ const params = (member.parameters ?? [])
538
905
  .map((p) => `${visit(p.name)} ${getType(p.type)}`)
539
906
  .join(', ');
540
907
  const returnType = member.type ? ` ${getType(member.type)}` : '';
541
908
  methods.push(`\t${methodName}(${params})${returnType}`);
542
909
  }
543
- else if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
910
+ else if (isPropertySignature(member) && isIdentifier(member.name)) {
544
911
  properties.push(`\t${member.name.text} ${getOptionalNodeType(member.type, !!member.questionToken)}`);
545
912
  }
546
913
  }
@@ -551,17 +918,17 @@ export function visit(node, options = {}) {
551
918
  const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
552
919
  return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}`;
553
920
  }
554
- else if (ts.isClassDeclaration(node)) {
921
+ else if (isClassDeclaration(node)) {
555
922
  if (options.addFunctionOutside) {
556
923
  outsideNodes.push(node);
557
924
  const className = visit(node.name);
558
925
  classNames.add(className);
559
926
  const properties = new Map();
560
927
  const methods = new Map();
561
- for (const member of node.members) {
928
+ for (const member of (node.members ?? [])) {
562
929
  const memberModifiers = member.modifiers;
563
- const isStatic = memberModifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
564
- if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
930
+ const isStatic = memberModifiers?.some((m) => m.kind === 'StaticKeyword');
931
+ if (isPropertyDeclaration(member) && isIdentifier(member.name)) {
565
932
  if (isStatic) {
566
933
  classStaticProps.add(`${className}.${member.name.text}`);
567
934
  }
@@ -569,7 +936,7 @@ export function visit(node, options = {}) {
569
936
  properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
570
937
  }
571
938
  }
572
- if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
939
+ if (isMethodDeclaration(member) && isIdentifier(member.name)) {
573
940
  if (isStatic) {
574
941
  classStaticMethods.add(`${className}.${member.name.text}`);
575
942
  }
@@ -586,9 +953,9 @@ export function visit(node, options = {}) {
586
953
  const typeParams = getTypeParameters(node.typeParameters);
587
954
  const typeParamNames = getTypeParameterNames(node.typeParameters);
588
955
  let parentClass = null;
589
- if (node.heritageClauses) {
590
- for (const clause of node.heritageClauses) {
591
- if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
956
+ if ((node.heritageClauses ?? [])) {
957
+ for (const clause of (node.heritageClauses ?? [])) {
958
+ if (clause.token?.kind === 'ExtendsKeyword') {
592
959
  parentClass = visit(clause.types[0].expression);
593
960
  }
594
961
  }
@@ -597,11 +964,11 @@ export function visit(node, options = {}) {
597
964
  if (parentClass) {
598
965
  fields.push(`\t${parentClass}`);
599
966
  }
600
- for (const member of node.members) {
601
- if (ts.isPropertyDeclaration(member)) {
967
+ for (const member of (node.members ?? [])) {
968
+ if (isPropertyDeclaration(member)) {
602
969
  const fieldName = visit(member.name);
603
970
  let fieldType;
604
- if (member.type && ts.isArrayTypeNode(member.type)) {
971
+ if (member.type && isArrayTypeNode(member.type)) {
605
972
  fieldType = `[]${getType(member.type, true)}`;
606
973
  }
607
974
  else {
@@ -611,13 +978,13 @@ export function visit(node, options = {}) {
611
978
  }
612
979
  }
613
980
  let result = `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}\n\n`;
614
- const ctor = node.members.find((m) => ts.isConstructorDeclaration(m));
981
+ const ctor = (node.members ?? []).find((m) => isConstructorDeclaration(m));
615
982
  if (ctor) {
616
- const ctorParameterInfo = getFunctionParametersInfo(ctor.parameters);
983
+ const ctorParameterInfo = getFunctionParametersInfo(ctor.parameters ?? []);
617
984
  const bodyStatements = ctor.body?.statements
618
985
  .filter((s) => {
619
- if (ts.isExpressionStatement(s) && ts.isCallExpression(s.expression)) {
620
- return s.expression.expression.kind !== ts.SyntaxKind.SuperKeyword;
986
+ if (isExpressionStatement(s) && isCallExpression(s.expression)) {
987
+ return s.expression.expression.kind !== 'SuperKeyword';
621
988
  }
622
989
  return true;
623
990
  })
@@ -628,12 +995,12 @@ export function visit(node, options = {}) {
628
995
  else {
629
996
  result += `func New${name}${typeParams}() *${name}${typeParamNames} {\n\t\treturn &${name}${typeParamNames}{}\n\t}\n\n`;
630
997
  }
631
- for (const member of node.members) {
632
- if (ts.isMethodDeclaration(member)) {
998
+ for (const member of (node.members ?? [])) {
999
+ if (isMethodDeclaration(member)) {
633
1000
  const methodName = visit(member.name);
634
- const methodParameterInfo = getFunctionParametersInfo(member.parameters);
1001
+ const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
635
1002
  const returnType = member.type ? ` ${getType(member.type)}` : '';
636
- const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
1003
+ const isStatic = member.modifiers?.some((m) => m.kind === 'StaticKeyword');
637
1004
  if (isStatic) {
638
1005
  // Static methods become package-level functions named ClassName_methodName
639
1006
  result += `func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
@@ -642,23 +1009,23 @@ export function visit(node, options = {}) {
642
1009
  result += `func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
643
1010
  }
644
1011
  }
645
- else if (ts.isGetAccessor(member)) {
1012
+ else if (isGetAccessor(member)) {
646
1013
  // Getter: get prop() { ... } → func (self *T) Prop() RetType { ... }
647
1014
  const getterName = visit(member.name);
648
1015
  const returnType = member.type ? ` ${getType(member.type)}` : ' interface{}';
649
1016
  result += `func (self *${name}${typeParamNames}) Get_${getterName}()${returnType} ${visit(member.body)}\n\n`;
650
1017
  }
651
- else if (ts.isSetAccessor(member)) {
1018
+ else if (isSetAccessor(member)) {
652
1019
  // Setter: set prop(val) { ... } → func (self *T) SetProp(val ValType) { ... }
653
1020
  const setterName = visit(member.name);
654
- const parameterInfo = getFunctionParametersInfo(member.parameters);
1021
+ const parameterInfo = getFunctionParametersInfo(member.parameters ?? []);
655
1022
  result += `func (self *${name}${typeParamNames}) Set_${setterName}(${parameterInfo.signature}) ${visit(member.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}\n\n`;
656
1023
  }
657
1024
  }
658
1025
  // Static property declarations → package-level vars named ClassName_propName
659
- for (const member of node.members) {
660
- if (ts.isPropertyDeclaration(member) &&
661
- member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword)) {
1026
+ for (const member of (node.members ?? [])) {
1027
+ if (isPropertyDeclaration(member) &&
1028
+ member.modifiers?.some((m) => m.kind === 'StaticKeyword')) {
662
1029
  const fieldName = visit(member.name);
663
1030
  const fieldType = getOptionalNodeType(member.type, !!member.questionToken);
664
1031
  const initializer = member.initializer ? ` = ${visit(member.initializer)}` : '';
@@ -667,7 +1034,7 @@ export function visit(node, options = {}) {
667
1034
  }
668
1035
  return result.trim();
669
1036
  }
670
- else if (ts.isNewExpression(node)) {
1037
+ else if (isNewExpression(node)) {
671
1038
  const className = visit(node.expression);
672
1039
  if (className === 'Promise') {
673
1040
  return visitNewPromise(node);
@@ -675,7 +1042,7 @@ export function visit(node, options = {}) {
675
1042
  if (className === 'RegExp') {
676
1043
  importedPackages.add('regexp');
677
1044
  const nodeArgs = node.arguments ?? [];
678
- if (nodeArgs.length >= 2 && ts.isStringLiteral(nodeArgs[1])) {
1045
+ if (nodeArgs.length >= 2 && isStringLiteral(nodeArgs[1])) {
679
1046
  const pattern = visit(nodeArgs[0]);
680
1047
  const flags = jsRegexFlagsToGo(nodeArgs[1].text);
681
1048
  return `regexp.MustCompile("${flags}" + ${pattern})`;
@@ -691,22 +1058,22 @@ export function visit(node, options = {}) {
691
1058
  if (className === 'Set') {
692
1059
  return visitNewSet(node);
693
1060
  }
694
- const typeArgs = getTypeArguments(node.typeArguments);
695
- 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)) : [];
696
1063
  return `New${className}${typeArgs}(${args.join(', ')})`;
697
1064
  }
698
- else if (ts.isObjectLiteralExpression(node)) {
1065
+ else if (isObjectLiteralExpression(node)) {
699
1066
  let typeName = '';
700
- if (ts.isVariableDeclaration(node.parent) && node.parent.type) {
1067
+ if (isVariableDeclaration(node.parent) && node.parent.type) {
701
1068
  typeName = getTypeText(node.parent.type);
702
1069
  }
703
- const properties = node.properties
1070
+ const properties = (node.properties ?? [])
704
1071
  .map((p) => {
705
- if (ts.isPropertyAssignment(p)) {
1072
+ if (isPropertyAssignment(p)) {
706
1073
  return `${visit(p.name)}: ${visit(p.initializer)}`;
707
1074
  }
708
1075
  // Shorthand: { name } → name: name
709
- if (ts.isShorthandPropertyAssignment(p)) {
1076
+ if (isShorthandPropertyAssignment(p)) {
710
1077
  const name = visit(p.name);
711
1078
  return `${name}: ${name}`;
712
1079
  }
@@ -717,32 +1084,32 @@ export function visit(node, options = {}) {
717
1084
  .join(', ');
718
1085
  return `${typeName}{${properties}}`;
719
1086
  }
720
- else if (ts.isPropertyAssignment(node)) {
1087
+ else if (isPropertyAssignment(node)) {
721
1088
  return `${visit(node.name)}: ${visit(node.initializer)}`;
722
1089
  }
723
- else if (ts.isNonNullExpression(node)) {
1090
+ else if (isNonNullExpression(node)) {
724
1091
  return visit(node.expression);
725
1092
  }
726
- else if (ts.isImportDeclaration(node)) {
1093
+ else if (isImportDeclaration(node)) {
727
1094
  return visitImportDeclaration(node);
728
1095
  }
729
- else if (ts.isExportDeclaration(node) || ts.isExportAssignment(node)) {
1096
+ else if (isExportDeclaration(node) || isExportAssignment(node)) {
730
1097
  return '';
731
1098
  }
732
- const syntaxKind = ts.SyntaxKind[node.kind];
1099
+ const syntaxKind = node.kind;
733
1100
  if (!['FirstStatement', 'EndOfFileToken'].includes(syntaxKind)) {
734
- const snippet = node.getText().substring(0, 60).replace(/\n/g, ' ');
1101
+ const snippet = String(node.text ?? node.kind).substring(0, 60).replace(/\n/g, ' ');
735
1102
  console.warn(`[TypeNative] Unsupported syntax: ${syntaxKind} — "${snippet}"`);
736
1103
  }
737
- ts.forEachChild(node, (subNode) => {
738
- code += visit(subNode);
739
- });
1104
+ for (const child of childNodes(node)) {
1105
+ code += visit(child);
1106
+ }
740
1107
  return code;
741
1108
  }
742
1109
  function getTypeText(typeNode) {
743
1110
  if (!typeNode)
744
1111
  return ':';
745
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1112
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
746
1113
  return typeNode.typeName.text;
747
1114
  }
748
1115
  return getType(typeNode);
@@ -752,8 +1119,8 @@ function toGoStringLiteral(value) {
752
1119
  }
753
1120
  function visitSpreadArrayLiteral(node, elemType) {
754
1121
  const chunks = [];
755
- for (const el of node.elements) {
756
- if (ts.isSpreadElement(el)) {
1122
+ for (const el of (node.elements ?? [])) {
1123
+ if (isSpreadElement(el)) {
757
1124
  chunks.push({ isSpread: true, items: [el.expression] });
758
1125
  }
759
1126
  else {
@@ -804,16 +1171,16 @@ function getTempName(prefix) {
804
1171
  }
805
1172
  function inferExpectedTypeFromContext(node) {
806
1173
  const parent = node.parent;
807
- if (ts.isVariableDeclaration(parent) && parent.initializer === node && parent.type) {
1174
+ if (isVariableDeclaration(parent) && parent.initializer === node && parent.type) {
808
1175
  return getType(parent.type);
809
1176
  }
810
- if (ts.isReturnStatement(parent)) {
1177
+ if (isReturnStatement(parent)) {
811
1178
  let scope = parent.parent;
812
1179
  while (scope) {
813
- if (ts.isFunctionDeclaration(scope) ||
814
- ts.isMethodDeclaration(scope) ||
815
- ts.isFunctionExpression(scope) ||
816
- ts.isArrowFunction(scope)) {
1180
+ if (isFunctionDeclaration(scope) ||
1181
+ isMethodDeclaration(scope) ||
1182
+ isFunctionExpression(scope) ||
1183
+ isArrowFunction(scope)) {
817
1184
  if (scope.type)
818
1185
  return getType(scope.type);
819
1186
  break;
@@ -825,9 +1192,9 @@ function inferExpectedTypeFromContext(node) {
825
1192
  }
826
1193
  function prescanVariableDeclarations(block) {
827
1194
  for (const stmt of block.statements) {
828
- if (ts.isVariableStatement(stmt)) {
1195
+ if (isVariableStatement(stmt)) {
829
1196
  for (const decl of stmt.declarationList.declarations) {
830
- if (ts.isIdentifier(decl.name) && !variableGoTypes.has(decl.name.text)) {
1197
+ if (isIdentifier(decl.name) && !variableGoTypes.has(decl.name.text)) {
831
1198
  if (decl.type) {
832
1199
  variableGoTypes.set(decl.name.text, getType(decl.type));
833
1200
  }
@@ -844,12 +1211,12 @@ function prescanVariableDeclarations(block) {
844
1211
  function inferFunctionBodyReturnType(node) {
845
1212
  if (node.type)
846
1213
  return getType(node.type);
847
- if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
1214
+ if (isArrowFunction(node) && !isBlock(node.body)) {
848
1215
  return inferExpressionType(node.body);
849
1216
  }
850
- if (node.body && ts.isBlock(node.body)) {
1217
+ if (node.body && isBlock(node.body)) {
851
1218
  for (const stmt of node.body.statements) {
852
- if (ts.isReturnStatement(stmt) && stmt.expression) {
1219
+ if (isReturnStatement(stmt) && stmt.expression) {
853
1220
  const exprType = inferExpressionType(stmt.expression);
854
1221
  if (exprType)
855
1222
  return exprType;
@@ -859,42 +1226,43 @@ function inferFunctionBodyReturnType(node) {
859
1226
  return undefined;
860
1227
  }
861
1228
  function inferArrowFunctionGoType(node) {
862
- 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(', ');
863
1230
  const retType = inferFunctionBodyReturnType(node);
864
1231
  return `func(${params})${retType ? ` ${retType}` : ''}`;
865
1232
  }
866
1233
  function inferExpressionType(expr) {
867
- if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
1234
+ if (isArrowFunction(expr) || isFunctionExpression(expr)) {
868
1235
  return inferArrowFunctionGoType(expr);
869
1236
  }
870
- if (ts.isParenthesizedExpression(expr))
1237
+ if (isParenthesizedExpression(expr))
871
1238
  return inferExpressionType(expr.expression);
872
- if (ts.isNonNullExpression(expr))
1239
+ if (isNonNullExpression(expr))
873
1240
  return inferExpressionType(expr.expression);
874
- if (ts.isAsExpression(expr))
1241
+ if (isAsExpression(expr))
875
1242
  return getType(expr.type);
876
- if (ts.isTypeAssertionExpression(expr))
1243
+ if (isTypeAssertionExpression(expr))
877
1244
  return getType(expr.type);
878
- if (ts.isStringLiteral(expr) ||
879
- ts.isNoSubstitutionTemplateLiteral(expr) ||
880
- ts.isTemplateExpression(expr)) {
1245
+ if (isStringLiteral(expr) ||
1246
+ isNoSubstitutionTemplateLiteral(expr) ||
1247
+ isTemplateExpression(expr)) {
881
1248
  return 'string';
882
1249
  }
883
- if (ts.isNumericLiteral(expr))
1250
+ if (isNumericLiteral(expr))
884
1251
  return 'float64';
885
- if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword)
1252
+ if (expr.kind === 'TrueKeyword' || expr.kind === 'FalseKeyword')
886
1253
  return 'bool';
887
- if (expr.kind === ts.SyntaxKind.NullKeyword)
1254
+ if (expr.kind === 'NullKeyword')
888
1255
  return 'nil';
889
- if (ts.isIdentifier(expr))
1256
+ if (isIdentifier(expr))
890
1257
  return variableGoTypes.get(expr.text);
891
- if (ts.isArrayLiteralExpression(expr)) {
892
- if (expr.elements.length === 0)
1258
+ if (isArrayLiteralExpression(expr)) {
1259
+ const elements = expr.elements ?? [];
1260
+ if (elements.length === 0)
893
1261
  return '[]interface{}';
894
- const firstElementType = inferExpressionType(expr.elements[0]) ?? 'interface{}';
1262
+ const firstElementType = inferExpressionType(elements[0]) ?? 'interface{}';
895
1263
  return `[]${firstElementType}`;
896
1264
  }
897
- if (ts.isNewExpression(expr) && ts.isIdentifier(expr.expression)) {
1265
+ if (isNewExpression(expr) && isIdentifier(expr.expression)) {
898
1266
  const ctorName = expr.expression.text;
899
1267
  if (ctorName === 'Map' && expr.typeArguments && expr.typeArguments.length === 2) {
900
1268
  return `map[${getType(expr.typeArguments[0])}]${getType(expr.typeArguments[1])}`;
@@ -903,8 +1271,8 @@ function inferExpressionType(expr) {
903
1271
  return `map[${getType(expr.typeArguments[0])}]struct{}`;
904
1272
  }
905
1273
  }
906
- if (ts.isPropertyAccessExpression(expr)) {
907
- if (ts.isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
1274
+ if (isPropertyAccessExpression(expr)) {
1275
+ if (isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
908
1276
  const enumType = getSafeName(expr.expression.text);
909
1277
  return enumType;
910
1278
  }
@@ -926,7 +1294,7 @@ function inferExpressionType(expr) {
926
1294
  if (resolvedPropertyType) {
927
1295
  return resolvedPropertyType;
928
1296
  }
929
- if (ts.isIdentifier(expr.expression)) {
1297
+ if (isIdentifier(expr.expression)) {
930
1298
  const className = variableClassNames.get(expr.expression.text);
931
1299
  const memberType = className
932
1300
  ? classPropertyTypes.get(className)?.get(expr.name.text)
@@ -935,7 +1303,7 @@ function inferExpressionType(expr) {
935
1303
  return memberType;
936
1304
  }
937
1305
  }
938
- if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
1306
+ if (isCallExpression(expr) && isPropertyAccessExpression(expr.expression)) {
939
1307
  const methodName = expr.expression.name.text;
940
1308
  const ownerType = inferExpressionType(expr.expression.expression);
941
1309
  if (ownerType && ownerType.startsWith('map[')) {
@@ -972,7 +1340,7 @@ function inferExpressionType(expr) {
972
1340
  return returnType;
973
1341
  }
974
1342
  }
975
- if (ts.isIdentifier(expr.expression.expression)) {
1343
+ if (isIdentifier(expr.expression.expression)) {
976
1344
  const className = variableClassNames.get(expr.expression.expression.text);
977
1345
  const returnType = className
978
1346
  ? classMethodReturnTypes.get(className)?.get(methodName)
@@ -985,15 +1353,15 @@ function inferExpressionType(expr) {
985
1353
  }
986
1354
  }
987
1355
  }
988
- if (ts.isConditionalExpression(expr)) {
1356
+ if (isConditionalExpression(expr)) {
989
1357
  const whenTrueType = inferExpressionType(expr.whenTrue);
990
1358
  const whenFalseType = inferExpressionType(expr.whenFalse);
991
1359
  if (whenTrueType && whenTrueType === whenFalseType)
992
1360
  return whenTrueType;
993
1361
  return whenTrueType ?? whenFalseType;
994
1362
  }
995
- if (ts.isBinaryExpression(expr) &&
996
- expr.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
1363
+ if (isBinaryExpression(expr) &&
1364
+ expr.operatorToken.kind === 'QuestionQuestionToken') {
997
1365
  const leftType = inferExpressionType(expr.left);
998
1366
  const rightType = inferExpressionType(expr.right);
999
1367
  if (leftType && leftType.startsWith('*') && rightType === leftType.slice(1)) {
@@ -1073,14 +1441,14 @@ function visitOptionalElementAccess(node) {
1073
1441
  return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${elementExpr} }()`;
1074
1442
  }
1075
1443
  function visitOptionalCall(node) {
1076
- if (!ts.isPropertyAccessExpression(node.expression)) {
1077
- 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(', ')})`;
1078
1446
  }
1079
1447
  const baseNode = node.expression.expression;
1080
1448
  const methodName = node.expression.name.text;
1081
1449
  const baseExpr = visit(baseNode);
1082
1450
  const baseType = inferExpressionType(baseNode);
1083
- const args = node.arguments.map((a) => visit(a)).join(', ');
1451
+ const args = (node.arguments ?? []).map((a) => visit(a)).join(', ');
1084
1452
  if (!baseType || !baseType.startsWith('*')) {
1085
1453
  return `${baseExpr}.${methodName}(${args})`;
1086
1454
  }
@@ -1105,18 +1473,18 @@ function getArrayElementTypeFromGoType(goType) {
1105
1473
  return elementType || 'interface{}';
1106
1474
  }
1107
1475
  function inferArrayCallbackReturnType(callback, elementType, fallbackType) {
1108
- if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
1476
+ if (isArrowFunction(callback) || isFunctionExpression(callback)) {
1109
1477
  if (callback.type) {
1110
1478
  const explicitType = getType(callback.type);
1111
1479
  return explicitType || fallbackType;
1112
1480
  }
1113
- if (ts.isBlock(callback.body)) {
1481
+ if (isBlock(callback.body)) {
1114
1482
  return fallbackType;
1115
1483
  }
1116
1484
  const inferred = inferExpressionType(callback.body);
1117
1485
  return inferred ?? fallbackType;
1118
1486
  }
1119
- if (ts.isIdentifier(callback)) {
1487
+ if (isIdentifier(callback)) {
1120
1488
  const knownType = variableGoTypes.get(callback.text);
1121
1489
  if (knownType)
1122
1490
  return knownType;
@@ -1124,7 +1492,7 @@ function inferArrayCallbackReturnType(callback, elementType, fallbackType) {
1124
1492
  return fallbackType;
1125
1493
  }
1126
1494
  function buildArrayCallbackInfo(callback, elementType, forcedReturnType) {
1127
- if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
1495
+ if (isArrowFunction(callback) || isFunctionExpression(callback)) {
1128
1496
  const paramCount = callback.parameters.length;
1129
1497
  const callbackReturnType = forcedReturnType ?? inferArrayCallbackReturnType(callback, elementType, 'interface{}');
1130
1498
  const params = [];
@@ -1137,7 +1505,7 @@ function buildArrayCallbackInfo(callback, elementType, forcedReturnType) {
1137
1505
  if (paramCount > 2) {
1138
1506
  params.push(`${visit(callback.parameters[2].name)} []${elementType}`);
1139
1507
  }
1140
- const body = ts.isBlock(callback.body)
1508
+ const body = isBlock(callback.body)
1141
1509
  ? visit(callback.body, { inline: true })
1142
1510
  : `{ return ${visit(callback.body)}; }`;
1143
1511
  return {
@@ -1176,7 +1544,7 @@ function buildArrayCallbackInvocationReduce(callbackInfo, accVar, itemVar, index
1176
1544
  return `(${callbackInfo.fnExpr})(${args.join(', ')})`;
1177
1545
  }
1178
1546
  function visitArrayHigherOrderCall(node) {
1179
- if (!ts.isPropertyAccessExpression(node.expression))
1547
+ if (!isPropertyAccessExpression(node.expression))
1180
1548
  return undefined;
1181
1549
  const methodName = node.expression.name.text;
1182
1550
  if (!['map', 'filter', 'some', 'find', 'findIndex', 'every', 'forEach', 'reduce', 'join'].includes(methodName)) {
@@ -1194,12 +1562,12 @@ function visitArrayHigherOrderCall(node) {
1194
1562
  return undefined;
1195
1563
  importedPackages.add('strings');
1196
1564
  importedPackages.add('fmt');
1197
- const separator = node.arguments[0] ? visit(node.arguments[0]) : '""';
1565
+ const separator = (node.arguments ?? [])[0] ? visit((node.arguments ?? [])[0]) : '""';
1198
1566
  const arrVar = getTempName('arrjoin');
1199
1567
  const partsVar = getTempName('parts');
1200
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}) }()`;
1201
1569
  }
1202
- const callback = node.arguments[0];
1570
+ const callback = (node.arguments ?? [])[0];
1203
1571
  if (!callback) {
1204
1572
  return undefined;
1205
1573
  }
@@ -1245,12 +1613,12 @@ function visitArrayHigherOrderCall(node) {
1245
1613
  return `func() { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${callbackCall} } }()`;
1246
1614
  }
1247
1615
  if (methodName === 'reduce') {
1248
- const reduceCallback = node.arguments[0];
1616
+ const reduceCallback = (node.arguments ?? [])[0];
1249
1617
  if (!reduceCallback)
1250
1618
  return undefined;
1251
- const initialValue = node.arguments[1] ? visit(node.arguments[1]) : undefined;
1619
+ const initialValue = (node.arguments ?? [])[1] ? visit((node.arguments ?? [])[1]) : undefined;
1252
1620
  const accType = initialValue
1253
- ? (inferExpressionType(node.arguments[1]) ?? elementType)
1621
+ ? (inferExpressionType((node.arguments ?? [])[1]) ?? elementType)
1254
1622
  : elementType;
1255
1623
  const accVar = getTempName('acc');
1256
1624
  // Build a callback that takes (acc, item, idx, arr) — param count determines what gets passed
@@ -1275,7 +1643,7 @@ function getAliasType(name, seen = new Set()) {
1275
1643
  return undefined;
1276
1644
  if (seen.has(name))
1277
1645
  return undefined;
1278
- if (ts.isTypeReferenceNode(aliasType) && ts.isIdentifier(aliasType.typeName)) {
1646
+ if (isTypeReferenceNode(aliasType) && isIdentifier(aliasType.typeName)) {
1279
1647
  const nestedName = aliasType.typeName.text;
1280
1648
  if (typeAliases.has(nestedName)) {
1281
1649
  seen.add(name);
@@ -1295,33 +1663,33 @@ function getOptionalNodeType(typeNode, isOptional) {
1295
1663
  return baseType;
1296
1664
  }
1297
1665
  function getEnumMemberName(name) {
1298
- if (ts.isIdentifier(name)) {
1666
+ if (isIdentifier(name)) {
1299
1667
  return getSafeName(name.text);
1300
1668
  }
1301
- if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
1669
+ if (isStringLiteral(name) || isNumericLiteral(name)) {
1302
1670
  const sanitized = name.text.replace(/[^a-zA-Z0-9_]/g, '_');
1303
1671
  return sanitized.length > 0 ? sanitized : 'Member';
1304
1672
  }
1305
1673
  return 'Member';
1306
1674
  }
1307
1675
  function getEnumBaseType(node) {
1308
- for (const member of node.members) {
1676
+ for (const member of (node.members ?? [])) {
1309
1677
  const initializer = member.initializer;
1310
1678
  if (!initializer)
1311
1679
  continue;
1312
- if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {
1680
+ if (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer)) {
1313
1681
  return 'string';
1314
1682
  }
1315
1683
  }
1316
1684
  return 'float64';
1317
1685
  }
1318
1686
  function readNumericEnumInitializer(initializer) {
1319
- if (ts.isNumericLiteral(initializer)) {
1687
+ if (isNumericLiteral(initializer)) {
1320
1688
  return Number(initializer.text);
1321
1689
  }
1322
- if (ts.isPrefixUnaryExpression(initializer) &&
1323
- initializer.operator === ts.SyntaxKind.MinusToken &&
1324
- ts.isNumericLiteral(initializer.operand)) {
1690
+ if (isPrefixUnaryExpression(initializer) &&
1691
+ initializer.operator === 'MinusToken' &&
1692
+ isNumericLiteral(initializer.operand)) {
1325
1693
  return -Number(initializer.operand.text);
1326
1694
  }
1327
1695
  return undefined;
@@ -1332,7 +1700,7 @@ function visitEnumDeclaration(node) {
1332
1700
  let nextNumericValue = 0;
1333
1701
  let canAutoIncrement = true;
1334
1702
  const members = [];
1335
- for (const member of node.members) {
1703
+ for (const member of (node.members ?? [])) {
1336
1704
  const memberName = getEnumMemberName(member.name);
1337
1705
  const symbolName = `${enumName}_${memberName}`;
1338
1706
  let valueExpr;
@@ -1368,15 +1736,15 @@ function visitEnumDeclaration(node) {
1368
1736
  function getType(typeNode, getArrayType = false) {
1369
1737
  if (!typeNode)
1370
1738
  return ':';
1371
- if (ts.isArrayTypeNode(typeNode)) {
1739
+ if (isArrayTypeNode(typeNode)) {
1372
1740
  const elementType = getType(typeNode.elementType);
1373
1741
  return getArrayType ? elementType : `[]${elementType}`;
1374
1742
  }
1375
1743
  // Handle union types (e.g. string | null, number | undefined)
1376
- if (ts.isUnionTypeNode(typeNode)) {
1377
- const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1378
- t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1379
- !(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'));
1380
1748
  if (nonNullTypes.length === 1 && nonNullTypes.length < typeNode.types.length) {
1381
1749
  // This is a nullable type T | null or T | undefined
1382
1750
  const innerType = getType(nonNullTypes[0]);
@@ -1389,14 +1757,14 @@ function getType(typeNode, getArrayType = false) {
1389
1757
  // Non-nullable union or multi-type union → interface{}
1390
1758
  return 'interface{}';
1391
1759
  }
1392
- if (ts.isFunctionTypeNode(typeNode)) {
1760
+ if (isFunctionTypeNode(typeNode)) {
1393
1761
  const params = typeNode.parameters
1394
1762
  .map((p) => (p.type ? getType(p.type) : 'interface{}'))
1395
1763
  .join(', ');
1396
1764
  const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
1397
1765
  return `func(${params})${ret}`;
1398
1766
  }
1399
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1767
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
1400
1768
  const name = typeNode.typeName.text;
1401
1769
  if (enumNames.has(name)) {
1402
1770
  return getSafeName(name);
@@ -1426,8 +1794,8 @@ function getType(typeNode, getArrayType = false) {
1426
1794
  }
1427
1795
  return `${name}${typeArgs}`;
1428
1796
  }
1429
- const type = TypeCheker.getTypeFromTypeNode(typeNode);
1430
- let typeName = TypeCheker.typeToString(type);
1797
+ // Syntactic replacement for the ts typechecker: render the type node's text
1798
+ let typeName = typeNodeToText(typeNode);
1431
1799
  const isArray = typeName.includes('[]');
1432
1800
  if (isArray) {
1433
1801
  if (getArrayType) {
@@ -1453,22 +1821,22 @@ function getType(typeNode, getArrayType = false) {
1453
1821
  function getTypeCategory(typeNode) {
1454
1822
  if (!typeNode)
1455
1823
  return undefined;
1456
- if (ts.isArrayTypeNode(typeNode))
1824
+ if (isArrayTypeNode(typeNode))
1457
1825
  return 'array';
1458
- if (typeNode.kind === ts.SyntaxKind.StringKeyword)
1826
+ if (typeNode.kind === 'StringKeyword')
1459
1827
  return 'string';
1460
- if (typeNode.kind === ts.SyntaxKind.NumberKeyword)
1828
+ if (typeNode.kind === 'NumberKeyword')
1461
1829
  return 'number';
1462
- if (typeNode.kind === ts.SyntaxKind.BooleanKeyword)
1830
+ if (typeNode.kind === 'BooleanKeyword')
1463
1831
  return 'boolean';
1464
- if (ts.isUnionTypeNode(typeNode)) {
1465
- const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1466
- t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1467
- !(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'));
1468
1836
  if (nonNullTypes.length === 1)
1469
1837
  return getTypeCategory(nonNullTypes[0]);
1470
1838
  }
1471
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1839
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
1472
1840
  const name = typeNode.typeName.text;
1473
1841
  if (name === 'Map')
1474
1842
  return 'Map';
@@ -1481,13 +1849,13 @@ function getTypeCategory(typeNode) {
1481
1849
  return undefined;
1482
1850
  }
1483
1851
  function getClassNameFromTypeNode(typeNode) {
1484
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1852
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
1485
1853
  return classNames.has(typeNode.typeName.text) ? typeNode.typeName.text : undefined;
1486
1854
  }
1487
- if (ts.isUnionTypeNode(typeNode)) {
1488
- const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1489
- t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1490
- !(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'));
1491
1859
  if (nonNullTypes.length === 1) {
1492
1860
  return getClassNameFromTypeNode(nonNullTypes[0]);
1493
1861
  }
@@ -1495,18 +1863,18 @@ function getClassNameFromTypeNode(typeNode) {
1495
1863
  return undefined;
1496
1864
  }
1497
1865
  function resolveExpressionType(expr) {
1498
- if (ts.isIdentifier(expr)) {
1866
+ if (isIdentifier(expr)) {
1499
1867
  return variableTypes.get(expr.text);
1500
1868
  }
1501
- if (expr.kind === ts.SyntaxKind.ThisKeyword) {
1869
+ if (expr.kind === 'ThisKeyword') {
1502
1870
  return 'class';
1503
1871
  }
1504
1872
  return undefined;
1505
1873
  }
1506
1874
  function isNilLiteral(node) {
1507
- if (node.kind === ts.SyntaxKind.NullKeyword)
1875
+ if (node.kind === 'NullKeyword')
1508
1876
  return true;
1509
- if (ts.isIdentifier(node) && node.text === 'undefined')
1877
+ if (isIdentifier(node) && node.text === 'undefined')
1510
1878
  return true;
1511
1879
  return false;
1512
1880
  }
@@ -1528,10 +1896,15 @@ function getAcessString(leftSide, rightSide, objectType) {
1528
1896
  return 'runtime.GOOS';
1529
1897
  }
1530
1898
  if (rightSide === 'env') {
1531
- // process.env.X is handled by nested property access; just return a placeholder object
1532
- return 'os.Environ()';
1899
+ // process.env.X is detected via the nested access below
1900
+ return 'process.env';
1533
1901
  }
1534
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
+ }
1535
1908
  return `${leftSide}.${rightSide}`;
1536
1909
  }
1537
1910
  const callHandlers = {
@@ -1887,7 +2260,9 @@ function getDynamicCallHandler(caller, objectType) {
1887
2260
  return null;
1888
2261
  }
1889
2262
  function getCallString(caller, args, typeArgs = '', objectType) {
1890
- 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);
1891
2266
  if (handler) {
1892
2267
  return handler(caller, args, typeArgs);
1893
2268
  }
@@ -1895,17 +2270,17 @@ function getCallString(caller, args, typeArgs = '', objectType) {
1895
2270
  }
1896
2271
  function getOperatorText(operator) {
1897
2272
  switch (operator) {
1898
- case ts.SyntaxKind.PlusToken:
2273
+ case 'PlusToken':
1899
2274
  return '+';
1900
- case ts.SyntaxKind.MinusToken:
2275
+ case 'MinusToken':
1901
2276
  return '-';
1902
- case ts.SyntaxKind.TildeToken:
2277
+ case 'TildeToken':
1903
2278
  return '~';
1904
- case ts.SyntaxKind.ExclamationToken:
2279
+ case 'ExclamationToken':
1905
2280
  return '!';
1906
- case ts.SyntaxKind.PlusPlusToken:
2281
+ case 'PlusPlusToken':
1907
2282
  return '++';
1908
- case ts.SyntaxKind.MinusMinusToken:
2283
+ case 'MinusMinusToken':
1909
2284
  return '--';
1910
2285
  default:
1911
2286
  console.error('Did not find operator', operator);
@@ -2021,12 +2396,12 @@ function getSafeName(name) {
2021
2396
  function getPromiseChannelType(node) {
2022
2397
  let parent = node.parent;
2023
2398
  while (parent) {
2024
- if (ts.isFunctionDeclaration(parent) ||
2025
- ts.isMethodDeclaration(parent) ||
2026
- ts.isFunctionExpression(parent)) {
2399
+ if (isFunctionDeclaration(parent) ||
2400
+ isMethodDeclaration(parent) ||
2401
+ isFunctionExpression(parent)) {
2027
2402
  if (parent.type &&
2028
- ts.isTypeReferenceNode(parent.type) &&
2029
- ts.isIdentifier(parent.type.typeName)) {
2403
+ isTypeReferenceNode(parent.type) &&
2404
+ isIdentifier(parent.type.typeName)) {
2030
2405
  if (parent.type.typeName.text === 'Promise' &&
2031
2406
  parent.type.typeArguments &&
2032
2407
  parent.type.typeArguments.length > 0) {
@@ -2041,7 +2416,7 @@ function getPromiseChannelType(node) {
2041
2416
  }
2042
2417
  function visitPromiseReturn(node, options) {
2043
2418
  const callback = node.arguments?.[0];
2044
- if (!callback || (!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback))) {
2419
+ if (!callback || (!isArrowFunction(callback) && !isFunctionExpression(callback))) {
2045
2420
  return `return ${visit(node)}` + (options.inline ? '' : ';\n\t');
2046
2421
  }
2047
2422
  const channelType = getPromiseChannelType(node);
@@ -2049,14 +2424,14 @@ function visitPromiseReturn(node, options) {
2049
2424
  const resolveName = resolveParam ? visit(resolveParam.name) : '';
2050
2425
  const prevResolveName = promiseResolveName;
2051
2426
  promiseResolveName = resolveName;
2052
- const body = ts.isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
2427
+ const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
2053
2428
  promiseResolveName = prevResolveName;
2054
2429
  return (`ch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch` +
2055
2430
  (options.inline ? '' : ';\n\t'));
2056
2431
  }
2057
2432
  function visitNewPromise(node) {
2058
2433
  const callback = node.arguments?.[0];
2059
- if (!callback || (!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback))) {
2434
+ if (!callback || (!isArrowFunction(callback) && !isFunctionExpression(callback))) {
2060
2435
  return 'NewPromise()';
2061
2436
  }
2062
2437
  const channelType = getPromiseChannelType(node);
@@ -2064,7 +2439,7 @@ function visitNewPromise(node) {
2064
2439
  const resolveName = resolveParam ? visit(resolveParam.name) : '';
2065
2440
  const prevResolveName = promiseResolveName;
2066
2441
  promiseResolveName = resolveName;
2067
- const body = ts.isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
2442
+ const body = isBlock(callback.body) ? visit(callback.body) : `{ ${visit(callback.body)} }`;
2068
2443
  promiseResolveName = prevResolveName;
2069
2444
  return `func() chan ${channelType} {\n\t\tch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch;\n\t}()`;
2070
2445
  }
@@ -2087,19 +2462,19 @@ function extractMapValueType(mapType) {
2087
2462
  function visitNewMap(node) {
2088
2463
  let keyType = 'interface{}';
2089
2464
  let valueType = 'interface{}';
2090
- if (node.typeArguments && node.typeArguments.length === 2) {
2091
- keyType = getType(node.typeArguments[0]);
2092
- 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]);
2093
2468
  }
2094
2469
  const mapType = `map[${keyType}]${valueType}`;
2095
- const args = node.arguments;
2096
- if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
2470
+ const args = (node.arguments ?? []);
2471
+ if (!args || args.length === 0 || !isArrayLiteralExpression(args[0])) {
2097
2472
  return `make(${mapType})`;
2098
2473
  }
2099
2474
  const initArg = args[0];
2100
2475
  const tmp = getTempName('map');
2101
2476
  const entries = initArg.elements
2102
- .filter((el) => ts.isArrayLiteralExpression(el) && el.elements.length >= 2)
2477
+ .filter((el) => isArrayLiteralExpression(el) && el.elements.length >= 2)
2103
2478
  .map((el) => {
2104
2479
  const pair = el;
2105
2480
  return `${tmp}[${visit(pair.elements[0])}] = ${visit(pair.elements[1])}`;
@@ -2109,12 +2484,12 @@ function visitNewMap(node) {
2109
2484
  }
2110
2485
  function visitNewSet(node) {
2111
2486
  let elementType = 'interface{}';
2112
- if (node.typeArguments && node.typeArguments.length === 1) {
2113
- elementType = getType(node.typeArguments[0]);
2487
+ if ((node.typeArguments ?? []) && (node.typeArguments ?? []).length === 1) {
2488
+ elementType = getType((node.typeArguments ?? [])[0]);
2114
2489
  }
2115
2490
  const setType = `map[${elementType}]struct{}`;
2116
- const args = node.arguments;
2117
- if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
2491
+ const args = (node.arguments ?? []);
2492
+ if (!args || args.length === 0 || !isArrayLiteralExpression(args[0])) {
2118
2493
  return `make(${setType})`;
2119
2494
  }
2120
2495
  const initArg = args[0];
@@ -2155,28 +2530,45 @@ function includeLocalImport(code, dir, goFileName) {
2155
2530
  }
2156
2531
  if (!goFileName) {
2157
2532
  // Inline mode (npm packages): pull declarations into the current transpilation context
2158
- const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
2533
+ const sf = parseFunction(code);
2159
2534
  for (const stmt of sf.statements) {
2160
2535
  visit(stmt, { addFunctionOutside: true });
2161
2536
  }
2162
2537
  currentFileDir = prevDir;
2163
2538
  return;
2164
2539
  }
2165
- // 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.
2166
2543
  const savedOutsideNodes = outsideNodes;
2167
2544
  const savedPackages = [...importedPackages];
2168
2545
  outsideNodes = [];
2169
2546
  importedPackages.clear();
2170
- const sf = ts.createSourceFile('imported.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
2547
+ helperProvidedPackages.clear();
2548
+ const sf = parseFunction(code);
2171
2549
  const inlineLines = [];
2172
2550
  for (const stmt of sf.statements) {
2173
2551
  const result = visit(stmt, { addFunctionOutside: true });
2174
2552
  if (result.trim())
2175
2553
  inlineLines.push(result);
2176
2554
  }
2177
- const fileImports = [...importedPackages].map((pkg) => `import "${pkg}"`).join('\n');
2178
2555
  const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
2179
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');
2180
2572
  const parts = ['package main'];
2181
2573
  if (fileImports)
2182
2574
  parts.push(fileImports);
@@ -2185,11 +2577,14 @@ function includeLocalImport(code, dir, goFileName) {
2185
2577
  if (fileOutside.trim())
2186
2578
  parts.push(fileOutside.trim());
2187
2579
  localImportFiles.set(goFileName, parts.join('\n\n'));
2188
- // 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.
2189
2582
  outsideNodes = savedOutsideNodes;
2190
2583
  importedPackages.clear();
2191
2584
  for (const p of savedPackages)
2192
2585
  importedPackages.add(p);
2586
+ for (const p of helperProvidedPackages)
2587
+ importedPackages.add(p);
2193
2588
  currentFileDir = prevDir;
2194
2589
  }
2195
2590
  function registerGoPackageAliases(node, goPkg) {
@@ -2202,14 +2597,14 @@ function registerGoPackageAliases(node, goPkg) {
2202
2597
  importAliases.set(clause.name.text, pkgName);
2203
2598
  }
2204
2599
  if (clause.namedBindings) {
2205
- if (ts.isNamespaceImport(clause.namedBindings)) {
2600
+ if (isNamespaceImport(clause.namedBindings)) {
2206
2601
  // `import * as myFmt from 'go:fmt'`
2207
2602
  const localName = clause.namedBindings.name.text;
2208
2603
  if (localName !== pkgName) {
2209
2604
  importAliases.set(localName, pkgName);
2210
2605
  }
2211
2606
  }
2212
- else if (ts.isNamedImports(clause.namedBindings)) {
2607
+ else if (isNamedImports(clause.namedBindings)) {
2213
2608
  // `import { Println, Sprintf as Spf } from 'go:fmt'`
2214
2609
  for (const el of clause.namedBindings.elements) {
2215
2610
  if (el.isTypeOnly)
@@ -2228,7 +2623,7 @@ function getImportLocalName(node) {
2228
2623
  if (clause.name)
2229
2624
  return clause.name.text;
2230
2625
  if (clause.namedBindings) {
2231
- if (ts.isNamespaceImport(clause.namedBindings))
2626
+ if (isNamespaceImport(clause.namedBindings))
2232
2627
  return clause.namedBindings.name.text;
2233
2628
  }
2234
2629
  return null;
@@ -2237,34 +2632,375 @@ function getImportLocalName(node) {
2237
2632
  // For default/namespace imports (e.g. `import path from 'node:path'`), entries are registered
2238
2633
  // as `callHandlers[localName.funcName]`. For named imports (e.g. `import { join } from 'node:path'`),
2239
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
+ }
2240
2651
  const nodeModuleMappings = {
2241
2652
  path: {
2242
- goPackage: 'path/filepath',
2243
2653
  functions: {
2244
- join: (args) => `filepath.Join(${args.join(', ')})`,
2245
- dirname: (args) => `filepath.Dir(${args[0]})`,
2246
- basename: (args) => args[1]
2247
- ? `strings.TrimSuffix(filepath.Base(${args[0]}), ${args[1]})`
2248
- : `filepath.Base(${args[0]})`,
2249
- extname: (args) => `filepath.Ext(${args[0]})`,
2250
- resolve: (args) => `func() string { p, _ := filepath.Abs(filepath.Join(${args.join(', ')})); return p }()`
2251
- }
2252
- }
2253
- // Future node stdlib modules can be added here as additional keys:
2254
- // fs: { goPackage: 'os', functions: { ... } },
2255
- // os: { goPackage: 'os', functions: { ... } },
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
+ }
2256
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
+ }
2257
2994
  // Mapping from Node.js stdlib module names to Go setup functions.
2258
2995
  // Each entry adds the required Go imports and registers call handlers for the local identifier.
2259
2996
  function setupNodeModuleImport(node, nodeModule) {
2260
2997
  const mapping = nodeModuleMappings[nodeModule];
2261
2998
  if (!mapping)
2262
2999
  return;
2263
- importedPackages.add(mapping.goPackage);
2264
3000
  const clause = node.importClause;
2265
3001
  if (!clause)
2266
3002
  return;
2267
- if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
3003
+ if (clause.namedBindings && isNamedImports(clause.namedBindings)) {
2268
3004
  // Named imports: `import { join, dirname } from 'node:path'`
2269
3005
  // Map each local name directly to its Go qualified function via importAliases,
2270
3006
  // so bare calls like `join(...)` resolve to `filepath.Join(...)`.
@@ -2290,7 +3026,7 @@ function setupNodeModuleImport(node, nodeModule) {
2290
3026
  }
2291
3027
  }
2292
3028
  function visitImportDeclaration(node) {
2293
- if (!ts.isStringLiteral(node.moduleSpecifier))
3029
+ if (!isStringLiteral(node.moduleSpecifier))
2294
3030
  return '';
2295
3031
  const moduleSpec = node.moduleSpecifier.text;
2296
3032
  // Relative imports → transpile to a separate Go file
@@ -2340,20 +3076,20 @@ function visitImportDeclaration(node) {
2340
3076
  if (clause.name) {
2341
3077
  defaultImportNamespaces.add(clause.name.text);
2342
3078
  }
2343
- else if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
3079
+ else if (clause.namedBindings && isNamespaceImport(clause.namedBindings)) {
2344
3080
  defaultImportNamespaces.add(clause.namedBindings.name.text);
2345
3081
  }
2346
3082
  }
2347
3083
  return '';
2348
3084
  }
2349
3085
  function getForOfVarNames(initializer) {
2350
- if (!ts.isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
3086
+ if (!isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
2351
3087
  return ['_'];
2352
3088
  }
2353
3089
  const decl = initializer.declarations[0];
2354
- if (ts.isArrayBindingPattern(decl.name)) {
3090
+ if (isArrayBindingPattern(decl.name)) {
2355
3091
  return decl.name.elements.map((el) => {
2356
- if (ts.isOmittedExpression(el))
3092
+ if (isOmittedExpression(el))
2357
3093
  return '_';
2358
3094
  return visit(el.name);
2359
3095
  });
@@ -2364,17 +3100,17 @@ function visitTryStatement(node, options) {
2364
3100
  const deferreds = [];
2365
3101
  // Register finally first (LIFO: runs last after catch)
2366
3102
  if (node.finallyBlock) {
2367
- const finallyBody = node.finallyBlock.statements.map((s) => visit(s)).join('\t');
3103
+ const finallyBody = (node.finallyBlock.statements ?? []).map((s) => visit(s)).join('\t');
2368
3104
  deferreds.push(`defer func() {\n\t\t\t${finallyBody}\t\t\t}()`);
2369
3105
  }
2370
3106
  // Register catch second (LIFO: runs first, handles panic via recover)
2371
3107
  if (node.catchClause) {
2372
3108
  const varDecl = node.catchClause.variableDeclaration;
2373
3109
  const catchVar = varDecl ? visit(varDecl.name) : '_r';
2374
- 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');
2375
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}()`);
2376
3112
  }
2377
- const tryBody = node.tryBlock.statements.map((s) => visit(s)).join('\t');
3113
+ const tryBody = (node.tryBlock.statements ?? []).map((s) => visit(s)).join('\t');
2378
3114
  const body = [...deferreds, tryBody].join('\n\t\t\t');
2379
3115
  return `func() {\n\t\t\t${body}\n\t\t\t}()` + (options.inline ? '' : ';\n\t');
2380
3116
  }