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