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/.github/workflows/npm-publish-github-packages.yml +13 -5
- package/CHANGELOG.md +32 -0
- package/TODO.md +34 -0
- package/bin/cli.js +62 -0
- package/bin/index.js +104 -132
- package/bin/main.js +31 -0
- package/bin/parse-native.js +37 -0
- package/bin/parse-node.js +110 -0
- package/bin/prompt.js +29 -0
- package/bin/transpiler.js +1046 -310
- package/package.json +5 -7
- package/tsparser/go.mod +14 -0
- package/tsparser/go.sum +22 -0
- package/tsparser/main.go +279 -0
- package/tsparser/tsparser.exe +0 -0
- package/types/typenative-npm.d.ts +44 -0
package/bin/transpiler.js
CHANGED
|
@@ -1,7 +1,255 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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 (
|
|
448
|
+
else if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
104
449
|
return toGoStringLiteral(node.text);
|
|
105
450
|
}
|
|
106
|
-
else if (
|
|
451
|
+
else if (isAsExpression(node)) {
|
|
107
452
|
return visit(node.expression);
|
|
108
453
|
}
|
|
109
|
-
else if (
|
|
454
|
+
else if (isTypeAssertionExpression(node)) {
|
|
110
455
|
return visit(node.expression);
|
|
111
456
|
}
|
|
112
|
-
else if (
|
|
457
|
+
else if (isTemplateExpression(node)) {
|
|
113
458
|
return visitTemplateExpression(node);
|
|
114
459
|
}
|
|
115
|
-
else if (
|
|
460
|
+
else if (isNumericLiteral(node)) {
|
|
116
461
|
return `float64(${node.text})`;
|
|
117
462
|
}
|
|
118
|
-
else if (
|
|
463
|
+
else if (isToken(node) && node.kind === 'TrueKeyword') {
|
|
119
464
|
return `true`;
|
|
120
465
|
}
|
|
121
|
-
else if (
|
|
466
|
+
else if (isToken(node) && node.kind === 'FalseKeyword') {
|
|
122
467
|
return `false`;
|
|
123
468
|
}
|
|
124
|
-
else if (
|
|
469
|
+
else if (isToken(node) && node.kind === 'NullKeyword') {
|
|
125
470
|
return `nil`;
|
|
126
471
|
}
|
|
127
|
-
else if (
|
|
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 (
|
|
138
|
-
const type =
|
|
139
|
-
const hasSpread = node.elements.some((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 (
|
|
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 (
|
|
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 (
|
|
501
|
+
else if (isPropertyAccessExpression(node)) {
|
|
157
502
|
if (hasQuestionDot(node)) {
|
|
158
503
|
return visitOptionalPropertyAccess(node);
|
|
159
504
|
}
|
|
160
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
524
|
+
else if (isVariableDeclaration(node)) {
|
|
180
525
|
// Object destructuring: const { x, y } = obj
|
|
181
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
232
|
-
|
|
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 &&
|
|
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 (
|
|
608
|
+
else if (isCallExpression(node)) {
|
|
264
609
|
if (hasQuestionDot(node) ||
|
|
265
|
-
(
|
|
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 (
|
|
270
|
-
|
|
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 &&
|
|
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 (
|
|
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 =
|
|
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) =>
|
|
642
|
+
const hasSpreadArg = (node.arguments ?? []).some((a) => isSpreadElement(a));
|
|
298
643
|
const args = hasSpreadArg
|
|
299
|
-
? node.arguments.map((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 (
|
|
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 (
|
|
653
|
+
else if (isPrefixUnaryExpression(node)) {
|
|
309
654
|
return `${getOperatorText(node.operator)}${visit(node.operand)}`;
|
|
310
655
|
}
|
|
311
|
-
else if (
|
|
656
|
+
else if (isPostfixUnaryExpression(node)) {
|
|
312
657
|
return `${visit(node.operand, { inline: true })}${getOperatorText(node.operator)}`;
|
|
313
658
|
}
|
|
314
|
-
else if (
|
|
659
|
+
else if (isConditionalExpression(node)) {
|
|
315
660
|
return visitConditionalExpression(node);
|
|
316
661
|
}
|
|
317
|
-
else if (
|
|
318
|
-
if (node.operatorToken.kind ===
|
|
662
|
+
else if (isBinaryExpression(node)) {
|
|
663
|
+
if (node.operatorToken.kind === 'QuestionQuestionToken') {
|
|
319
664
|
return visitNullishCoalescingExpression(node);
|
|
320
665
|
}
|
|
321
|
-
let op = node.operatorToken
|
|
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 (
|
|
683
|
+
else if (isParenthesizedExpression(node)) {
|
|
329
684
|
return `(${visit(node.expression)})`;
|
|
330
685
|
}
|
|
331
|
-
else if (
|
|
686
|
+
else if (isAwaitExpression(node)) {
|
|
332
687
|
return `<-${visit(node.expression)}`;
|
|
333
688
|
}
|
|
334
|
-
else if (
|
|
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 (
|
|
693
|
+
else if (isExpressionStatement(node)) {
|
|
339
694
|
return visit(node.expression) + (options.inline ? '' : ';\n\t');
|
|
340
695
|
}
|
|
341
|
-
else if (
|
|
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 (
|
|
347
|
-
const varName =
|
|
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 (
|
|
707
|
+
else if (isForOfStatement(node)) {
|
|
353
708
|
// Unwrap Object.entries(x) → treat x as the iterable
|
|
354
709
|
let iterNode = node.expression;
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
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 (
|
|
736
|
+
else if (isWhileStatement(node)) {
|
|
382
737
|
return `for ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
|
|
383
738
|
}
|
|
384
|
-
else if (
|
|
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 (
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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 (
|
|
766
|
+
else if (isSwitchStatement(node)) {
|
|
400
767
|
return `switch ${visit(node.expression)} ${visit(node.caseBlock)}`;
|
|
401
768
|
}
|
|
402
|
-
else if (
|
|
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 (
|
|
406
|
-
const isFallThrough = !node.statements.some((c) =>
|
|
407
|
-
return `case ${visit(node.expression, { inline: true })}: \n\t\t\t${node.statements
|
|
408
|
-
.filter((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 (
|
|
413
|
-
return `default: \n\t\t\t${node.statements
|
|
414
|
-
.filter((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 (
|
|
785
|
+
else if (isBreakStatement(node)) {
|
|
419
786
|
return 'break';
|
|
420
787
|
}
|
|
421
|
-
else if (
|
|
788
|
+
else if (isThrowStatement(node)) {
|
|
422
789
|
const expr = node.expression;
|
|
423
|
-
if (
|
|
424
|
-
|
|
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 (
|
|
799
|
+
else if (isTryStatement(node)) {
|
|
433
800
|
return visitTryStatement(node, options);
|
|
434
801
|
}
|
|
435
|
-
else if (
|
|
802
|
+
else if (isReturnStatement(node)) {
|
|
436
803
|
// Handle return new Promise(...)
|
|
437
804
|
if (node.expression &&
|
|
438
|
-
|
|
439
|
-
|
|
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 (
|
|
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 &&
|
|
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 (
|
|
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 && !
|
|
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 (!
|
|
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 ===
|
|
856
|
+
else if (node.kind === 'ThisKeyword') {
|
|
490
857
|
return 'self';
|
|
491
858
|
}
|
|
492
|
-
else if (
|
|
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 (
|
|
869
|
+
else if (isTypeAliasDeclaration(node)) {
|
|
503
870
|
typeAliases.set(node.name.text, node.type);
|
|
504
871
|
return '';
|
|
505
872
|
}
|
|
506
|
-
else if (
|
|
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 (
|
|
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 ===
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 ===
|
|
564
|
-
if (
|
|
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 (
|
|
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 ===
|
|
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 (
|
|
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 &&
|
|
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) =>
|
|
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 (
|
|
620
|
-
return s.expression.expression.kind !==
|
|
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 (
|
|
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 ===
|
|
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 (
|
|
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 (
|
|
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 (
|
|
661
|
-
member.modifiers?.some((m) => m.kind ===
|
|
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 (
|
|
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 &&
|
|
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 (
|
|
1065
|
+
else if (isObjectLiteralExpression(node)) {
|
|
699
1066
|
let typeName = '';
|
|
700
|
-
if (
|
|
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 (
|
|
1072
|
+
if (isPropertyAssignment(p)) {
|
|
706
1073
|
return `${visit(p.name)}: ${visit(p.initializer)}`;
|
|
707
1074
|
}
|
|
708
1075
|
// Shorthand: { name } → name: name
|
|
709
|
-
if (
|
|
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 (
|
|
1087
|
+
else if (isPropertyAssignment(node)) {
|
|
721
1088
|
return `${visit(node.name)}: ${visit(node.initializer)}`;
|
|
722
1089
|
}
|
|
723
|
-
else if (
|
|
1090
|
+
else if (isNonNullExpression(node)) {
|
|
724
1091
|
return visit(node.expression);
|
|
725
1092
|
}
|
|
726
|
-
else if (
|
|
1093
|
+
else if (isImportDeclaration(node)) {
|
|
727
1094
|
return visitImportDeclaration(node);
|
|
728
1095
|
}
|
|
729
|
-
else if (
|
|
1096
|
+
else if (isExportDeclaration(node) || isExportAssignment(node)) {
|
|
730
1097
|
return '';
|
|
731
1098
|
}
|
|
732
|
-
const syntaxKind =
|
|
1099
|
+
const syntaxKind = node.kind;
|
|
733
1100
|
if (!['FirstStatement', 'EndOfFileToken'].includes(syntaxKind)) {
|
|
734
|
-
const snippet = node.
|
|
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
|
-
|
|
738
|
-
code += visit(
|
|
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 (
|
|
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 (
|
|
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 (
|
|
1174
|
+
if (isVariableDeclaration(parent) && parent.initializer === node && parent.type) {
|
|
808
1175
|
return getType(parent.type);
|
|
809
1176
|
}
|
|
810
|
-
if (
|
|
1177
|
+
if (isReturnStatement(parent)) {
|
|
811
1178
|
let scope = parent.parent;
|
|
812
1179
|
while (scope) {
|
|
813
|
-
if (
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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 (
|
|
1195
|
+
if (isVariableStatement(stmt)) {
|
|
829
1196
|
for (const decl of stmt.declarationList.declarations) {
|
|
830
|
-
if (
|
|
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 (
|
|
1214
|
+
if (isArrowFunction(node) && !isBlock(node.body)) {
|
|
848
1215
|
return inferExpressionType(node.body);
|
|
849
1216
|
}
|
|
850
|
-
if (node.body &&
|
|
1217
|
+
if (node.body && isBlock(node.body)) {
|
|
851
1218
|
for (const stmt of node.body.statements) {
|
|
852
|
-
if (
|
|
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 (
|
|
1234
|
+
if (isArrowFunction(expr) || isFunctionExpression(expr)) {
|
|
868
1235
|
return inferArrowFunctionGoType(expr);
|
|
869
1236
|
}
|
|
870
|
-
if (
|
|
1237
|
+
if (isParenthesizedExpression(expr))
|
|
871
1238
|
return inferExpressionType(expr.expression);
|
|
872
|
-
if (
|
|
1239
|
+
if (isNonNullExpression(expr))
|
|
873
1240
|
return inferExpressionType(expr.expression);
|
|
874
|
-
if (
|
|
1241
|
+
if (isAsExpression(expr))
|
|
875
1242
|
return getType(expr.type);
|
|
876
|
-
if (
|
|
1243
|
+
if (isTypeAssertionExpression(expr))
|
|
877
1244
|
return getType(expr.type);
|
|
878
|
-
if (
|
|
879
|
-
|
|
880
|
-
|
|
1245
|
+
if (isStringLiteral(expr) ||
|
|
1246
|
+
isNoSubstitutionTemplateLiteral(expr) ||
|
|
1247
|
+
isTemplateExpression(expr)) {
|
|
881
1248
|
return 'string';
|
|
882
1249
|
}
|
|
883
|
-
if (
|
|
1250
|
+
if (isNumericLiteral(expr))
|
|
884
1251
|
return 'float64';
|
|
885
|
-
if (expr.kind ===
|
|
1252
|
+
if (expr.kind === 'TrueKeyword' || expr.kind === 'FalseKeyword')
|
|
886
1253
|
return 'bool';
|
|
887
|
-
if (expr.kind ===
|
|
1254
|
+
if (expr.kind === 'NullKeyword')
|
|
888
1255
|
return 'nil';
|
|
889
|
-
if (
|
|
1256
|
+
if (isIdentifier(expr))
|
|
890
1257
|
return variableGoTypes.get(expr.text);
|
|
891
|
-
if (
|
|
892
|
-
|
|
1258
|
+
if (isArrayLiteralExpression(expr)) {
|
|
1259
|
+
const elements = expr.elements ?? [];
|
|
1260
|
+
if (elements.length === 0)
|
|
893
1261
|
return '[]interface{}';
|
|
894
|
-
const firstElementType = inferExpressionType(
|
|
1262
|
+
const firstElementType = inferExpressionType(elements[0]) ?? 'interface{}';
|
|
895
1263
|
return `[]${firstElementType}`;
|
|
896
1264
|
}
|
|
897
|
-
if (
|
|
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 (
|
|
907
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
996
|
-
expr.operatorToken.kind ===
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
1666
|
+
if (isIdentifier(name)) {
|
|
1299
1667
|
return getSafeName(name.text);
|
|
1300
1668
|
}
|
|
1301
|
-
if (
|
|
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 (
|
|
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 (
|
|
1687
|
+
if (isNumericLiteral(initializer)) {
|
|
1320
1688
|
return Number(initializer.text);
|
|
1321
1689
|
}
|
|
1322
|
-
if (
|
|
1323
|
-
initializer.operator ===
|
|
1324
|
-
|
|
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 (
|
|
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 (
|
|
1377
|
-
const nonNullTypes = typeNode.types.filter((t) => t.kind !==
|
|
1378
|
-
t.kind !==
|
|
1379
|
-
!(
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
1430
|
-
let typeName =
|
|
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 (
|
|
1824
|
+
if (isArrayTypeNode(typeNode))
|
|
1457
1825
|
return 'array';
|
|
1458
|
-
if (typeNode.kind ===
|
|
1826
|
+
if (typeNode.kind === 'StringKeyword')
|
|
1459
1827
|
return 'string';
|
|
1460
|
-
if (typeNode.kind ===
|
|
1828
|
+
if (typeNode.kind === 'NumberKeyword')
|
|
1461
1829
|
return 'number';
|
|
1462
|
-
if (typeNode.kind ===
|
|
1830
|
+
if (typeNode.kind === 'BooleanKeyword')
|
|
1463
1831
|
return 'boolean';
|
|
1464
|
-
if (
|
|
1465
|
-
const nonNullTypes = typeNode.types.filter((t) => t.kind !==
|
|
1466
|
-
t.kind !==
|
|
1467
|
-
!(
|
|
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 (
|
|
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 (
|
|
1852
|
+
if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
|
|
1485
1853
|
return classNames.has(typeNode.typeName.text) ? typeNode.typeName.text : undefined;
|
|
1486
1854
|
}
|
|
1487
|
-
if (
|
|
1488
|
-
const nonNullTypes = typeNode.types.filter((t) => t.kind !==
|
|
1489
|
-
t.kind !==
|
|
1490
|
-
!(
|
|
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 (
|
|
1866
|
+
if (isIdentifier(expr)) {
|
|
1499
1867
|
return variableTypes.get(expr.text);
|
|
1500
1868
|
}
|
|
1501
|
-
if (expr.kind ===
|
|
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 ===
|
|
1875
|
+
if (node.kind === 'NullKeyword')
|
|
1508
1876
|
return true;
|
|
1509
|
-
if (
|
|
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
|
|
1532
|
-
return '
|
|
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
|
-
|
|
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
|
|
2273
|
+
case 'PlusToken':
|
|
1899
2274
|
return '+';
|
|
1900
|
-
case
|
|
2275
|
+
case 'MinusToken':
|
|
1901
2276
|
return '-';
|
|
1902
|
-
case
|
|
2277
|
+
case 'TildeToken':
|
|
1903
2278
|
return '~';
|
|
1904
|
-
case
|
|
2279
|
+
case 'ExclamationToken':
|
|
1905
2280
|
return '!';
|
|
1906
|
-
case
|
|
2281
|
+
case 'PlusPlusToken':
|
|
1907
2282
|
return '++';
|
|
1908
|
-
case
|
|
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 (
|
|
2025
|
-
|
|
2026
|
-
|
|
2399
|
+
if (isFunctionDeclaration(parent) ||
|
|
2400
|
+
isMethodDeclaration(parent) ||
|
|
2401
|
+
isFunctionExpression(parent)) {
|
|
2027
2402
|
if (parent.type &&
|
|
2028
|
-
|
|
2029
|
-
|
|
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 || (!
|
|
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 =
|
|
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 || (!
|
|
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 =
|
|
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 || !
|
|
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) =>
|
|
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 || !
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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) =>
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
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 &&
|
|
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 (!
|
|
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 &&
|
|
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 (!
|
|
3086
|
+
if (!isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
|
|
2351
3087
|
return ['_'];
|
|
2352
3088
|
}
|
|
2353
3089
|
const decl = initializer.declarations[0];
|
|
2354
|
-
if (
|
|
3090
|
+
if (isArrayBindingPattern(decl.name)) {
|
|
2355
3091
|
return decl.name.elements.map((el) => {
|
|
2356
|
-
if (
|
|
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
|
}
|