typenative 0.0.15 → 0.0.17

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/CHANGELOG.md CHANGED
@@ -5,28 +5,77 @@ All notable changes to TypeNative will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.0.17] - 2026-02-16
9
+
10
+ ### Added
11
+
12
+ - Support for ternary expressions (`condition ? a : b`)
13
+ - Support for optional chaining (`obj?.prop`, `arr?.[i]`)
14
+ - Support for nullish coalescing (`??` operator)
15
+ - Support for type aliases (`type X = ...`)
16
+ - Support for type assertions (`expr as Type`)
17
+ - Support for optional properties (`prop?: Type` in interfaces/types)
18
+ - Support for default parameter values (e.g. `function(x = defaultValue)`)
19
+ - Support for enum declarations and member access (`enum X { ... }`, `X.Member`)
20
+ - Support for array methods: `map`, `filter`, `some`, `find`
21
+ - Support for chaining array methods (e.g. `arr.map(...).filter(...).join(...)`)
22
+
23
+ ## [0.0.16] - 2025-02-15
24
+
25
+ ### Added
26
+
27
+ - RegExp support: regex literals (`/pattern/flags`), `new RegExp()`, `test()`, and `exec()` mapped to Go's `regexp` package
28
+ - Universal `toString()` support for numbers, booleans, arrays, and objects
29
+
30
+ ## [0.0.15] - 2025-02-15
31
+
32
+ ### Added
33
+
34
+ - Classes with constructors, inheritance (`extends`, `super()`), and methods transpiled to Go structs
35
+ - Interfaces with method signatures and `extends` transpiled to Go interfaces
36
+ - Generics support for functions and classes via Go type parameters
37
+ - Async/Await transpiled to Go channels and goroutines
38
+ - Promises (`new Promise`) transpiled to channel + goroutine pattern
39
+ - `setTimeout` mapped to Go's `time.AfterFunc`
40
+ - Nullable types (`T | null`, `T | undefined`) transpiled to Go pointer types
41
+ - Object literals transpiled to Go struct literals
42
+ - `assert()` function transpiled to `panic` on failure
43
+ - `parseInt` and `parseFloat` mapped to Go's `strconv` package
44
+ - String methods: `split`, `trim`, `trimStart`, `trimEnd`, `toUpperCase`, `toLowerCase`, `indexOf`, `includes`, `startsWith`, `endsWith`, `replace`, `replaceAll`, `repeat`, `charAt`, `substring`, `slice`, `concat`, `toString`
45
+ - Array methods: `join`, `slice`, `toString` (in addition to existing `push`)
46
+ - Math methods: `ceil`, `round`, `abs`, `sqrt`, `pow`, `min`, `max` (in addition to existing `random`, `floor`)
47
+ - Non-null assertion operator (`!`) support
48
+ - Type-aware method dispatch to prevent class methods from being intercepted as built-in string/array methods
49
+ - Safe name collision avoidance for Go reserved identifiers
50
+ - Automatic Go import management for `fmt`, `math`, `math/rand`, `strings`, `strconv`, `time`
51
+
8
52
  ## [0.0.14] - 2025-05-25
9
53
 
10
54
  ### Added
55
+
11
56
  - Project creation with new `--new` command
12
57
  - Support for functions and arrow functions
13
58
  - Support for switch statements
14
59
  - If statements, while loops, and for...of loops for arrays
15
60
 
16
61
  ### Changed
62
+
17
63
  - Replaced shelljs dependency with execa for improved process execution
18
64
 
19
65
  ### Fixed
66
+
20
67
  - Project creation issues
21
68
 
22
69
  ## [0.0.12] - 2025-05-23
23
70
 
24
71
  ### Changed
72
+
25
73
  - Switched transpilation target to Go language
26
74
 
27
75
  ## [0.0.9] - 2024-03-01
28
76
 
29
77
  ### Added
78
+
30
79
  - Basic types support (number, boolean, string, null, any)
31
80
  - Support for variable declarations
32
81
  - Support for binary expressions
@@ -35,10 +84,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
35
84
  ## [0.0.1] - 2024-02-24
36
85
 
37
86
  ### Added
87
+
38
88
  - Initial project setup
39
89
  - Basic project structure
40
90
 
41
91
  ## [0.0.0] - 2022-12-20
42
92
 
43
93
  ### Added
94
+
44
95
  - Initial commit
package/README.md CHANGED
@@ -11,55 +11,142 @@ Build native applications using Typescript.
11
11
 
12
12
  - Write a file `test.ts` with content `console.log('Hello World!');` or any other message
13
13
  - Run `npx typenative --source test.ts --script`
14
- - See your message in the terminal
15
14
 
16
- ## Create a TypeNative Project
15
+ ## Typescript Syntax Support
17
16
 
18
- - Run `npx typenative --new`
19
- - Give your project a name
20
- - Start writing code
17
+ TypeNative currently supports a focused subset of TypeScript syntax elements that are transpiled to Go code. The support is grouped by topic for easier scanning.
21
18
 
22
- ## Typescript Syntax Support
19
+ **Basic Types**
20
+
21
+ | Feature | Supported | Notes |
22
+ | -------------- | :-------: | ------------------------------------------------------------- |
23
+ | number | ✅ | Transpiled to `float64` |
24
+ | boolean | ✅ | Transpiled to `bool` |
25
+ | string | ✅ | |
26
+ | null | ✅ | |
27
+ | any | ✅ | Used for type inference |
28
+ | Nullable types | ✅ | `T \| null` / `T \| undefined` transpiled to Go pointer types |
29
+
30
+ **Variables & Objects**
31
+
32
+ | Feature | Supported | Notes |
33
+ | --------------------- | :-------: | -------------------------------- |
34
+ | Variable declarations | ✅ | `let` and `const` |
35
+ | Object literals | ✅ | Transpiled to Go struct literals |
36
+
37
+ **Operators**
38
+
39
+ | Feature | Supported | Notes |
40
+ | ------------------------ | :-------: | ------------------------------ |
41
+ | Arithmetic operators | ✅ | `+`, `-`, etc. |
42
+ | Comparison operators | ✅ | `==`, `!=`, `===`, `!==`, etc. |
43
+ | Logical operators | ✅ | `&&`, `\|\|` |
44
+ | Increment/Decrement | ✅ | `++`, `--` |
45
+ | Non-null assertion (`!`) | ✅ | Stripped during transpilation |
46
+ | Ternary expressions | ✅ | `condition ? a : b` |
47
+ | Nullish coalescing | ✅ | `??` operator |
48
+ | Optional chaining | ✅ | `obj?.prop`, `arr?.[i]` |
49
+
50
+ **Control Flow**
51
+
52
+ | Feature | Supported | Notes |
53
+ | ------------------ | :-------: | ---------------------------------- |
54
+ | If/Else statements | ✅ | Fully supported |
55
+ | Switch statements | ✅ | Case and default statements |
56
+ | For loops | ✅ | Standard `for` loops |
57
+ | For...of loops | ✅ | Iteration over arrays |
58
+ | While loops | ✅ | Transpiled to Go's `for` loops |
59
+ | Do...while loops | ✅ | Implemented with conditional break |
60
+
61
+ **Data Structures & Array Methods**
62
+
63
+ | Feature | Supported | Notes |
64
+ | -------------------------- | :-------: | ---------------------------------------------------------------- |
65
+ | Arrays | ✅ | Basic array operations |
66
+ | Array methods | ✅ | `push`, `join`, `slice` |
67
+ | Higher-order array methods | ✅ | `.map()`, `.filter()`, `.some()`, `.find()` |
68
+ | Method chaining | ✅ | Chaining array methods such as `.map(...).filter(...).join(...)` |
69
+
70
+ **Functions**
71
+
72
+ | Feature | Supported | Notes |
73
+ | ---------------------------- | :-------: | --------------------------------- |
74
+ | Function declarations | ✅ | Transpiled to Go functions |
75
+ | Arrow functions | ✅ | Transpiled to anonymous functions |
76
+ | Generics (functions/classes) | ✅ | Type parameters via Go generics |
77
+ | Default parameter values | ✅ | `function(x = defaultValue)` |
78
+
79
+ **Classes & Interfaces**
80
+
81
+ | Feature | Supported | Notes |
82
+ | ------------------- | :-------: | -------------------------------------------------------------- |
83
+ | Classes | ✅ | Transpiled to Go structs with constructor and receiver methods |
84
+ | Class inheritance | ✅ | `extends` via embedded structs, `super()` supported |
85
+ | Interfaces | ✅ | Transpiled to Go interfaces, supports `extends` |
86
+ | Optional properties | ✅ | `prop?: Type` in interfaces/types |
87
+ | Enums | ✅ | `enum` declarations and member access |
88
+
89
+ **Async & Timing**
90
+
91
+ | Feature | Supported | Notes |
92
+ | ----------- | :-------: | ----------------------------------------------------------------- |
93
+ | Async/Await | ✅ | `async` functions return Go channels, `await` reads from channels |
94
+ | Promises | ✅ | `new Promise` transpiled to channel + goroutine pattern |
95
+ | setTimeout | ✅ | Mapped to Go's `time.AfterFunc` |
96
+
97
+ **Built-in Functions & Utilities**
98
+
99
+ | Feature | Supported | Notes |
100
+ | --------------------- | :-------: | ----------------------------------------------------- |
101
+ | console.log | ✅ | Mapped to `fmt.Println` |
102
+ | console.time/timeEnd | ✅ | Performance measurement via `time.Now` / `time.Since` |
103
+ | assert | ✅ | Transpiled to `panic` on failure |
104
+ | parseInt / parseFloat | ✅ | Mapped to Go's `strconv` package |
105
+
106
+ **Math Methods**
107
+
108
+ | Feature | Supported | Notes |
109
+ | ------------------------- | :-------: | ------------------------------------------------- |
110
+ | Math.random | ✅ | Mapped to `rand.Float64()` |
111
+ | Math.floor / ceil / round | ✅ | Mapped to `math.Floor`, `math.Ceil`, `math.Round` |
112
+ | Math.abs / sqrt / pow | ✅ | Mapped to corresponding `math` functions |
113
+ | Math.min / max | ✅ | Mapped to `math.Min`, `math.Max` |
114
+
115
+ **String Methods**
116
+
117
+ | Feature | Supported | Notes |
118
+ | -------------------------- | :-------: | --------------------------------------------- |
119
+ | Template literals | ✅ | Backtick strings with `${expr}` interpolation |
120
+ | toUpperCase / toLowerCase | ✅ | Via `strings` package |
121
+ | trim / trimStart / trimEnd | ✅ | Via `strings` package |
122
+ | split / includes / indexOf | ✅ | Via `strings` package |
123
+ | startsWith / endsWith | ✅ | Via `strings` package |
124
+ | replace / replaceAll | ✅ | Via `strings` package |
125
+ | charAt / substring / slice | ✅ | Direct Go string indexing/slicing |
126
+ | concat / repeat | ✅ | String concatenation and `strings.Repeat` |
127
+
128
+ **Number / Object Methods**
129
+
130
+ | Feature | Supported | Notes |
131
+ | -------- | :-------: | ----------------------------------------------------- |
132
+ | toString | ✅ | Universal `toString()` via `fmt.Sprintf` for any type |
133
+
134
+ **RegExp**
135
+
136
+ | Feature | Supported | Notes |
137
+ | -------------- | :-------: | --------------------------------------------------- |
138
+ | Regex literals | ✅ | `/pattern/flags` transpiled to `regexp.MustCompile` |
139
+ | new RegExp() | ✅ | Constructor with optional flags |
140
+ | test() | ✅ | Mapped to `regexp.MatchString` |
141
+ | exec() | ✅ | Mapped to `regexp.FindStringSubmatch` |
142
+
143
+ **Unsupported / Roadmap**
144
+
145
+ | Feature | Supported | Notes |
146
+ | --------------------------- | :-------: | ------------------------------------------------ |
147
+ | Modules/Imports | ❌ | `import` / `export` declarations |
148
+ | Try/Catch | ❌ | Error handling |
149
+ | Map / Set | ❌ | Built-in collection types and their methods |
150
+ | Closures over mutable state | ❌ | Functions capturing and mutating outer variables |
23
151
 
24
- TypeNative currently supports a subset of TypeScript syntax elements that are transpiled to Go code:
25
-
26
- | Feature | Supported | Notes |
27
- |---------|:---------:|-------|
28
- | **Basic Types** | | |
29
- | number | ✅ | Transpiled to `float64` |
30
- | boolean | ✅ | Transpiled to `bool` |
31
- | string | ✅ | |
32
- | null | ✅ | |
33
- | any | ✅ | Used for type inference |
34
- | **Variables** | | |
35
- | Variable declarations | ✅ | `let` and `const` |
36
- | **Operators** | | |
37
- | Arithmetic operators | ✅ | `+`, `-`, etc. |
38
- | Comparison operators | ✅ | `==`, `!=`, etc. |
39
- | Logical operators | ✅ | `&&`, `\|\|` |
40
- | Increment/Decrement | ✅ | `++`, `--` |
41
- | **Control Flow** | | |
42
- | For loops | ✅ | Standard `for` loops |
43
- | For...of loops | ✅ | Iteration over arrays |
44
- | While loops | ✅ | Transpiled to Go's `for` loops |
45
- | Do...while loops | ✅ | Implemented with conditional break |
46
- | If/Else statements | ✅ | Fully supported |
47
- | Switch statements | ✅ | Case and default statements |
48
- | **Data Structures** | | |
49
- | Arrays | ✅ | Basic array operations |
50
- | Array methods | ✅ | `push` supported |
51
- | **Functions** | | |
52
- | Function declarations | ✅ | Transpiled to Go functions |
53
- | Arrow Functions | ✅ | Transpiled to anonymous functions |
54
- | console.log | ✅ | Mapped to `fmt.Println` |
55
- | console.time/timeEnd | ✅ | Performance measurement |
56
- | Math.random | ✅ | Mapped to Go's `rand.Float64()` |
57
- | Math.floor | ✅ | Mapped to Go's `math.Floor()` |
58
- | **Unsupported Features** | | |
59
- | Classes | ❌ | Not implemented |
60
- | Interfaces | ❌ | Not implemented |
61
- | Async/Await | ❌ | Not implemented |
62
- | Modules/Imports | ❌ | Not implemented |
63
- | Generics | ❌ | Not implemented |
64
-
65
- TypeNative is currently in early development and new features are being added regularly.
152
+ TypeNative is currently in early development and new features are being added regularly. The goal for `1.0` release is for TypeNative to transpile itself.
package/bin/index.js CHANGED
@@ -105,7 +105,7 @@ function getPackageJson(projectName) {
105
105
  build: `npx typenative --source main.ts --output bin/${projectName}.exe`
106
106
  },
107
107
  devDependencies: {
108
- typenative: '^0.0.15'
108
+ typenative: '^0.0.16'
109
109
  }
110
110
  };
111
111
  return JSON.stringify(pckg, null, 2);
package/bin/transpiler.js CHANGED
@@ -9,6 +9,14 @@ let promiseResolveName = '';
9
9
  const dangerousNames = new Set(['main']);
10
10
  const renamedFunctions = new Map();
11
11
  const variableTypes = new Map();
12
+ const variableGoTypes = new Map();
13
+ const variableClassNames = new Map();
14
+ const classPropertyTypes = new Map();
15
+ const classMethodReturnTypes = new Map();
16
+ const interfacePropertyTypes = new Map();
17
+ const typeAliases = new Map();
18
+ const enumNames = new Set();
19
+ const enumBaseTypes = new Map();
12
20
  export function transpileToNative(code) {
13
21
  const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
14
22
  TypeCheker = ts.createProgram(['main.ts'], {}).getTypeChecker();
@@ -18,6 +26,14 @@ export function transpileToNative(code) {
18
26
  promiseResolveName = '';
19
27
  renamedFunctions.clear();
20
28
  variableTypes.clear();
29
+ variableGoTypes.clear();
30
+ variableClassNames.clear();
31
+ classPropertyTypes.clear();
32
+ classMethodReturnTypes.clear();
33
+ interfacePropertyTypes.clear();
34
+ typeAliases.clear();
35
+ enumNames.clear();
36
+ enumBaseTypes.clear();
21
37
  const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
22
38
  const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
23
39
  return `package main
@@ -43,8 +59,17 @@ export function visit(node, options = {}) {
43
59
  return 'nil';
44
60
  return getSafeName(node.text);
45
61
  }
46
- else if (ts.isStringLiteral(node)) {
47
- return `"${node.text}"`;
62
+ else if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
63
+ return toGoStringLiteral(node.text);
64
+ }
65
+ else if (ts.isAsExpression(node)) {
66
+ return visit(node.expression);
67
+ }
68
+ else if (ts.isTypeAssertionExpression(node)) {
69
+ return visit(node.expression);
70
+ }
71
+ else if (ts.isTemplateExpression(node)) {
72
+ return visitTemplateExpression(node);
48
73
  }
49
74
  else if (ts.isNumericLiteral(node)) {
50
75
  return `float64(${node.text})`;
@@ -58,17 +83,38 @@ export function visit(node, options = {}) {
58
83
  else if (ts.isToken(node) && node.kind === ts.SyntaxKind.NullKeyword) {
59
84
  return `nil`;
60
85
  }
86
+ else if (ts.isRegularExpressionLiteral(node)) {
87
+ importedPackages.add('regexp');
88
+ const text = node.text; // e.g. /pattern/flags
89
+ const lastSlash = text.lastIndexOf('/');
90
+ const pattern = text.substring(1, lastSlash);
91
+ const flags = text.substring(lastSlash + 1);
92
+ const goFlags = jsRegexFlagsToGo(flags);
93
+ const escaped = pattern.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
94
+ return `regexp.MustCompile("${goFlags}${escaped}")`;
95
+ }
61
96
  else if (ts.isArrayLiteralExpression(node)) {
62
97
  const type = ts.isVariableDeclaration(node.parent) ? getType(node.parent.type, true) : '';
63
98
  return `[]${type} {${node.elements.map((e) => visit(e)).join(', ')}}`;
64
99
  }
65
100
  else if (ts.isBlock(node)) {
66
- return `{\n\t\t${node.statements.map((n) => visit(n)).join('\t')}${options.extraBlockContent ?? ''}}${options.inline ? '' : '\n\t'}`;
101
+ return `{\n\t\t${options.prefixBlockContent ?? ''}${node.statements
102
+ .map((n) => visit(n))
103
+ .join('\t')}${options.extraBlockContent ?? ''}}${options.inline ? '' : '\n\t'}`;
67
104
  }
68
105
  else if (ts.isElementAccessExpression(node)) {
106
+ if (hasQuestionDot(node)) {
107
+ return visitOptionalElementAccess(node);
108
+ }
69
109
  return `${visit(node.expression)}[int(${visit(node.argumentExpression)})]`;
70
110
  }
71
111
  else if (ts.isPropertyAccessExpression(node)) {
112
+ if (hasQuestionDot(node)) {
113
+ return visitOptionalPropertyAccess(node);
114
+ }
115
+ if (ts.isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
116
+ return `${getSafeName(node.expression.text)}_${getEnumMemberName(node.name)}`;
117
+ }
72
118
  const leftSide = visit(node.expression);
73
119
  const rightSide = visit(node.name);
74
120
  const objectType = resolveExpressionType(node.expression);
@@ -78,15 +124,37 @@ export function visit(node, options = {}) {
78
124
  const type = getType(node.type);
79
125
  // Track variable type for type-aware method dispatch
80
126
  if (ts.isIdentifier(node.name)) {
127
+ if (node.type) {
128
+ variableGoTypes.set(node.name.text, getType(node.type));
129
+ }
81
130
  const cat = node.type ? getTypeCategory(node.type) : undefined;
82
131
  if (cat) {
83
132
  variableTypes.set(node.name.text, cat);
133
+ if (cat === 'class' && node.type) {
134
+ const className = getClassNameFromTypeNode(node.type);
135
+ if (className) {
136
+ variableClassNames.set(node.name.text, className);
137
+ }
138
+ }
84
139
  }
85
140
  else if (node.initializer &&
86
141
  ts.isNewExpression(node.initializer) &&
87
142
  ts.isIdentifier(node.initializer.expression)) {
88
- if (classNames.has(node.initializer.expression.text)) {
143
+ if (node.initializer.expression.text === 'RegExp') {
144
+ variableTypes.set(node.name.text, 'RegExp');
145
+ }
146
+ else if (classNames.has(node.initializer.expression.text)) {
89
147
  variableTypes.set(node.name.text, 'class');
148
+ variableClassNames.set(node.name.text, node.initializer.expression.text);
149
+ }
150
+ }
151
+ else if (node.initializer && ts.isRegularExpressionLiteral(node.initializer)) {
152
+ variableTypes.set(node.name.text, 'RegExp');
153
+ }
154
+ if (!variableGoTypes.has(node.name.text) && node.initializer) {
155
+ const inferredType = inferExpressionType(node.initializer);
156
+ if (inferredType) {
157
+ variableGoTypes.set(node.name.text, inferredType);
90
158
  }
91
159
  }
92
160
  }
@@ -103,6 +171,10 @@ export function visit(node, options = {}) {
103
171
  return `${type === ':' ? '' : 'var '}${visit(node.name)} ${type}${type === ':' ? '' : ' '}${initializer}`;
104
172
  }
105
173
  else if (ts.isCallExpression(node)) {
174
+ if (hasQuestionDot(node) ||
175
+ (ts.isPropertyAccessExpression(node.expression) && hasQuestionDot(node.expression))) {
176
+ return visitOptionalCall(node);
177
+ }
106
178
  // Handle setTimeout specially to get raw delay value
107
179
  if (ts.isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
108
180
  importedPackages.add('time');
@@ -111,6 +183,10 @@ export function visit(node, options = {}) {
111
183
  const delay = ts.isNumericLiteral(delayNode) ? delayNode.text : visit(delayNode);
112
184
  return `time.AfterFunc(${delay} * time.Millisecond, ${callback.trimEnd()})`;
113
185
  }
186
+ const arrayHigherOrderCall = visitArrayHigherOrderCall(node);
187
+ if (arrayHigherOrderCall) {
188
+ return arrayHigherOrderCall;
189
+ }
114
190
  const caller = visit(node.expression);
115
191
  const safeCaller = getSafeName(caller);
116
192
  const typeArgs = getTypeArguments(node.typeArguments);
@@ -128,7 +204,13 @@ export function visit(node, options = {}) {
128
204
  else if (ts.isPostfixUnaryExpression(node)) {
129
205
  return `${visit(node.operand, { inline: true })}${getOperatorText(node.operator)}`;
130
206
  }
207
+ else if (ts.isConditionalExpression(node)) {
208
+ return visitConditionalExpression(node);
209
+ }
131
210
  else if (ts.isBinaryExpression(node)) {
211
+ if (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
212
+ return visitNullishCoalescingExpression(node);
213
+ }
132
214
  let op = node.operatorToken.getText();
133
215
  if (op === '===')
134
216
  op = '==';
@@ -217,28 +299,56 @@ export function visit(node, options = {}) {
217
299
  const name = visit(node.name, { inline: true });
218
300
  const safeName = getSafeName(name);
219
301
  const typeParams = getTypeParameters(node.typeParameters);
220
- const parameters = node.parameters
221
- .map((p) => `${visit(p.name)} ${getType(p.type)}`)
222
- .join(', ');
302
+ const parameterInfo = getFunctionParametersInfo(node.parameters);
223
303
  const returnType = node.type ? ` ${getType(node.type)}` : '';
224
304
  if (options.isOutside) {
225
- return `func ${safeName}${typeParams}(${parameters})${returnType} ${visit(node.body)}`;
305
+ return `func ${safeName}${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
306
+ prefixBlockContent: parameterInfo.prefixBlockContent
307
+ })}`;
226
308
  }
227
- return `${safeName} := func${typeParams}(${parameters})${returnType} ${visit(node.body)}`;
309
+ return `${safeName} := func${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
310
+ prefixBlockContent: parameterInfo.prefixBlockContent
311
+ })}`;
228
312
  }
229
313
  else if (ts.isArrowFunction(node)) {
230
- const parameters = node.parameters
231
- .map((p) => `${visit(p.name)} ${getType(p.type)}`)
232
- .join(', ');
314
+ const parameterInfo = getFunctionParametersInfo(node.parameters);
233
315
  const returnType = node.type ? ` ${getType(node.type)}` : '';
234
- return `func(${parameters})${returnType} ${visit(node.body)}`;
316
+ if (parameterInfo.prefixBlockContent && !ts.isBlock(node.body)) {
317
+ return `func(${parameterInfo.signature})${returnType} {\n\t\t${parameterInfo.prefixBlockContent}return ${visit(node.body)};\n\t}`;
318
+ }
319
+ return `func(${parameterInfo.signature})${returnType} ${visit(node.body, {
320
+ prefixBlockContent: parameterInfo.prefixBlockContent
321
+ })}`;
235
322
  }
236
323
  else if (node.kind === ts.SyntaxKind.ThisKeyword) {
237
324
  return 'self';
238
325
  }
326
+ else if (ts.isEnumDeclaration(node)) {
327
+ const enumName = node.name.text;
328
+ enumNames.add(enumName);
329
+ enumBaseTypes.set(enumName, getEnumBaseType(node));
330
+ if (options.addFunctionOutside) {
331
+ outsideNodes.push(node);
332
+ return '';
333
+ }
334
+ return visitEnumDeclaration(node);
335
+ }
336
+ else if (ts.isTypeAliasDeclaration(node)) {
337
+ typeAliases.set(node.name.text, node.type);
338
+ return '';
339
+ }
239
340
  else if (ts.isInterfaceDeclaration(node)) {
240
341
  if (options.addFunctionOutside) {
241
342
  outsideNodes.push(node);
343
+ const properties = new Map();
344
+ for (const member of node.members) {
345
+ if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
346
+ properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
347
+ }
348
+ }
349
+ if (properties.size > 0) {
350
+ interfacePropertyTypes.set(visit(node.name), properties);
351
+ }
242
352
  return '';
243
353
  }
244
354
  const name = visit(node.name);
@@ -254,6 +364,7 @@ export function visit(node, options = {}) {
254
364
  }
255
365
  }
256
366
  const methods = [];
367
+ const properties = [];
257
368
  for (const member of node.members) {
258
369
  if (ts.isMethodSignature(member)) {
259
370
  const methodName = visit(member.name);
@@ -263,6 +374,13 @@ export function visit(node, options = {}) {
263
374
  const returnType = member.type ? ` ${getType(member.type)}` : '';
264
375
  methods.push(`\t${methodName}(${params})${returnType}`);
265
376
  }
377
+ else if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
378
+ properties.push(`\t${member.name.text} ${getOptionalNodeType(member.type, !!member.questionToken)}`);
379
+ }
380
+ }
381
+ if (properties.length > 0 && methods.length === 0) {
382
+ const fields = [...extendedInterfaces.map((e) => `\t${e}`), ...properties];
383
+ return `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}`;
266
384
  }
267
385
  const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
268
386
  return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}`;
@@ -270,7 +388,20 @@ export function visit(node, options = {}) {
270
388
  else if (ts.isClassDeclaration(node)) {
271
389
  if (options.addFunctionOutside) {
272
390
  outsideNodes.push(node);
273
- classNames.add(visit(node.name));
391
+ const className = visit(node.name);
392
+ classNames.add(className);
393
+ const properties = new Map();
394
+ const methods = new Map();
395
+ for (const member of node.members) {
396
+ if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
397
+ properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
398
+ }
399
+ if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
400
+ methods.set(member.name.text, member.type ? getType(member.type) : 'interface{}');
401
+ }
402
+ }
403
+ classPropertyTypes.set(className, properties);
404
+ classMethodReturnTypes.set(className, methods);
274
405
  return '';
275
406
  }
276
407
  const name = visit(node.name);
@@ -296,7 +427,7 @@ export function visit(node, options = {}) {
296
427
  fieldType = `[]${getType(member.type, true)}`;
297
428
  }
298
429
  else {
299
- fieldType = member.type ? getType(member.type) : 'interface{}';
430
+ fieldType = getOptionalNodeType(member.type, !!member.questionToken);
300
431
  }
301
432
  fields.push(`\t${fieldName} ${fieldType}`);
302
433
  }
@@ -304,7 +435,7 @@ export function visit(node, options = {}) {
304
435
  let result = `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}\n\n`;
305
436
  const ctor = node.members.find((m) => ts.isConstructorDeclaration(m));
306
437
  if (ctor) {
307
- const params = ctor.parameters.map((p) => `${visit(p.name)} ${getType(p.type)}`).join(', ');
438
+ const ctorParameterInfo = getFunctionParametersInfo(ctor.parameters);
308
439
  const bodyStatements = ctor.body?.statements
309
440
  .filter((s) => {
310
441
  if (ts.isExpressionStatement(s) && ts.isCallExpression(s.expression)) {
@@ -314,7 +445,7 @@ export function visit(node, options = {}) {
314
445
  })
315
446
  .map((s) => visit(s))
316
447
  .join('\t') ?? '';
317
- result += `func New${name}${typeParams}(${params}) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${bodyStatements}return self;\n\t}\n\n`;
448
+ result += `func New${name}${typeParams}(${ctorParameterInfo.signature}) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${ctorParameterInfo.prefixBlockContent}${bodyStatements}return self;\n\t}\n\n`;
318
449
  }
319
450
  else {
320
451
  result += `func New${name}${typeParams}() *${name}${typeParamNames} {\n\t\treturn &${name}${typeParamNames}{}\n\t}\n\n`;
@@ -322,11 +453,11 @@ export function visit(node, options = {}) {
322
453
  for (const member of node.members) {
323
454
  if (ts.isMethodDeclaration(member)) {
324
455
  const methodName = visit(member.name);
325
- const params = member.parameters
326
- .map((p) => `${visit(p.name)} ${getType(p.type)}`)
327
- .join(', ');
456
+ const methodParameterInfo = getFunctionParametersInfo(member.parameters);
328
457
  const returnType = member.type ? ` ${getType(member.type)}` : '';
329
- result += `func (self *${name}${typeParamNames}) ${methodName}(${params})${returnType} ${visit(member.body)}\n\n`;
458
+ result += `func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, {
459
+ prefixBlockContent: methodParameterInfo.prefixBlockContent
460
+ })}\n\n`;
330
461
  }
331
462
  }
332
463
  return result.trim();
@@ -336,6 +467,19 @@ export function visit(node, options = {}) {
336
467
  if (className === 'Promise') {
337
468
  return visitNewPromise(node);
338
469
  }
470
+ if (className === 'RegExp') {
471
+ importedPackages.add('regexp');
472
+ const nodeArgs = node.arguments ?? [];
473
+ if (nodeArgs.length >= 2 && ts.isStringLiteral(nodeArgs[1])) {
474
+ const pattern = visit(nodeArgs[0]);
475
+ const flags = jsRegexFlagsToGo(nodeArgs[1].text);
476
+ return `regexp.MustCompile("${flags}" + ${pattern})`;
477
+ }
478
+ if (nodeArgs.length >= 1) {
479
+ return `regexp.MustCompile(${visit(nodeArgs[0])})`;
480
+ }
481
+ return `regexp.MustCompile("")`;
482
+ }
339
483
  const typeArgs = getTypeArguments(node.typeArguments);
340
484
  const args = node.arguments ? node.arguments.map((a) => visit(a)) : [];
341
485
  return `New${className}${typeArgs}(${args.join(', ')})`;
@@ -379,9 +523,492 @@ function getTypeText(typeNode) {
379
523
  }
380
524
  return getType(typeNode);
381
525
  }
526
+ function toGoStringLiteral(value) {
527
+ return JSON.stringify(value);
528
+ }
529
+ function visitTemplateExpression(node) {
530
+ const parts = [];
531
+ if (node.head.text.length > 0) {
532
+ parts.push(toGoStringLiteral(node.head.text));
533
+ }
534
+ for (const span of node.templateSpans) {
535
+ importedPackages.add('fmt');
536
+ parts.push(`fmt.Sprintf("%v", ${visit(span.expression)})`);
537
+ if (span.literal.text.length > 0) {
538
+ parts.push(toGoStringLiteral(span.literal.text));
539
+ }
540
+ }
541
+ if (parts.length === 0) {
542
+ return '""';
543
+ }
544
+ return parts.join(' + ');
545
+ }
546
+ function hasQuestionDot(node) {
547
+ return ('questionDotToken' in node &&
548
+ !!node.questionDotToken);
549
+ }
550
+ function getTempName(prefix) {
551
+ return `__${prefix}_${goSafeId()}__`;
552
+ }
553
+ function inferExpectedTypeFromContext(node) {
554
+ const parent = node.parent;
555
+ if (ts.isVariableDeclaration(parent) && parent.initializer === node && parent.type) {
556
+ return getType(parent.type);
557
+ }
558
+ if (ts.isReturnStatement(parent)) {
559
+ let scope = parent.parent;
560
+ while (scope) {
561
+ if (ts.isFunctionDeclaration(scope) ||
562
+ ts.isMethodDeclaration(scope) ||
563
+ ts.isFunctionExpression(scope) ||
564
+ ts.isArrowFunction(scope)) {
565
+ if (scope.type)
566
+ return getType(scope.type);
567
+ break;
568
+ }
569
+ scope = scope.parent;
570
+ }
571
+ }
572
+ return undefined;
573
+ }
574
+ function inferExpressionType(expr) {
575
+ if (ts.isParenthesizedExpression(expr))
576
+ return inferExpressionType(expr.expression);
577
+ if (ts.isNonNullExpression(expr))
578
+ return inferExpressionType(expr.expression);
579
+ if (ts.isAsExpression(expr))
580
+ return getType(expr.type);
581
+ if (ts.isTypeAssertionExpression(expr))
582
+ return getType(expr.type);
583
+ if (ts.isStringLiteral(expr) ||
584
+ ts.isNoSubstitutionTemplateLiteral(expr) ||
585
+ ts.isTemplateExpression(expr)) {
586
+ return 'string';
587
+ }
588
+ if (ts.isNumericLiteral(expr))
589
+ return 'float64';
590
+ if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword)
591
+ return 'bool';
592
+ if (expr.kind === ts.SyntaxKind.NullKeyword)
593
+ return 'nil';
594
+ if (ts.isIdentifier(expr))
595
+ return variableGoTypes.get(expr.text);
596
+ if (ts.isArrayLiteralExpression(expr)) {
597
+ if (expr.elements.length === 0)
598
+ return '[]interface{}';
599
+ const firstElementType = inferExpressionType(expr.elements[0]) ?? 'interface{}';
600
+ return `[]${firstElementType}`;
601
+ }
602
+ if (ts.isPropertyAccessExpression(expr)) {
603
+ if (ts.isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
604
+ const enumType = getSafeName(expr.expression.text);
605
+ return enumType;
606
+ }
607
+ const leftType = inferExpressionType(expr.expression);
608
+ if (expr.name.text === 'length')
609
+ return 'float64';
610
+ const resolvedLeftType = leftType?.replace(/^\*/, '').replace(/\[.*\]$/, '');
611
+ const resolvedPropertyType = resolvedLeftType
612
+ ? (classPropertyTypes.get(resolvedLeftType)?.get(expr.name.text) ??
613
+ interfacePropertyTypes.get(resolvedLeftType)?.get(expr.name.text))
614
+ : undefined;
615
+ if (hasQuestionDot(expr)) {
616
+ if (leftType && leftType.startsWith('*')) {
617
+ const memberType = resolvedPropertyType ?? 'interface{}';
618
+ return makeNullableType(memberType);
619
+ }
620
+ return 'interface{}';
621
+ }
622
+ if (resolvedPropertyType) {
623
+ return resolvedPropertyType;
624
+ }
625
+ if (ts.isIdentifier(expr.expression)) {
626
+ const className = variableClassNames.get(expr.expression.text);
627
+ const memberType = className
628
+ ? classPropertyTypes.get(className)?.get(expr.name.text)
629
+ : undefined;
630
+ if (memberType)
631
+ return memberType;
632
+ }
633
+ }
634
+ if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
635
+ const methodName = expr.expression.name.text;
636
+ const ownerType = inferExpressionType(expr.expression.expression);
637
+ if (isArrayLikeGoType(ownerType)) {
638
+ const elementType = getArrayElementTypeFromGoType(ownerType);
639
+ if (methodName === 'map') {
640
+ const callback = expr.arguments[0];
641
+ const mappedType = callback
642
+ ? inferArrayCallbackReturnType(callback, elementType, elementType)
643
+ : elementType;
644
+ return `[]${mappedType}`;
645
+ }
646
+ if (methodName === 'filter')
647
+ return `[]${elementType}`;
648
+ if (methodName === 'some')
649
+ return 'bool';
650
+ if (methodName === 'find')
651
+ return elementType;
652
+ if (methodName === 'join')
653
+ return 'string';
654
+ }
655
+ if (ownerType && ownerType.startsWith('*')) {
656
+ const className = ownerType.replace(/^\*/, '').replace(/\[.*\]$/, '');
657
+ const returnType = classMethodReturnTypes.get(className)?.get(methodName);
658
+ if (returnType) {
659
+ if (hasQuestionDot(expr) || hasQuestionDot(expr.expression)) {
660
+ return makeNullableType(returnType);
661
+ }
662
+ return returnType;
663
+ }
664
+ }
665
+ if (ts.isIdentifier(expr.expression.expression)) {
666
+ const className = variableClassNames.get(expr.expression.expression.text);
667
+ const returnType = className
668
+ ? classMethodReturnTypes.get(className)?.get(methodName)
669
+ : undefined;
670
+ if (returnType) {
671
+ if (hasQuestionDot(expr) || hasQuestionDot(expr.expression)) {
672
+ return makeNullableType(returnType);
673
+ }
674
+ return returnType;
675
+ }
676
+ }
677
+ }
678
+ if (ts.isConditionalExpression(expr)) {
679
+ const whenTrueType = inferExpressionType(expr.whenTrue);
680
+ const whenFalseType = inferExpressionType(expr.whenFalse);
681
+ if (whenTrueType && whenTrueType === whenFalseType)
682
+ return whenTrueType;
683
+ return whenTrueType ?? whenFalseType;
684
+ }
685
+ if (ts.isBinaryExpression(expr) &&
686
+ expr.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
687
+ const leftType = inferExpressionType(expr.left);
688
+ const rightType = inferExpressionType(expr.right);
689
+ if (leftType && leftType.startsWith('*') && rightType === leftType.slice(1)) {
690
+ return rightType;
691
+ }
692
+ return rightType ?? leftType;
693
+ }
694
+ return undefined;
695
+ }
696
+ function makeNullableType(typeName) {
697
+ if (!typeName || typeName === 'interface{}' || typeName.startsWith('*'))
698
+ return typeName || 'interface{}';
699
+ if (['string', 'float64', 'bool'].includes(typeName))
700
+ return `*${typeName}`;
701
+ return typeName;
702
+ }
703
+ function visitConditionalExpression(node) {
704
+ const whenTrue = visit(node.whenTrue);
705
+ const whenFalse = visit(node.whenFalse);
706
+ const resultType = inferExpectedTypeFromContext(node) ||
707
+ (() => {
708
+ const whenTrueType = inferExpressionType(node.whenTrue);
709
+ const whenFalseType = inferExpressionType(node.whenFalse);
710
+ if (whenTrueType && whenTrueType === whenFalseType)
711
+ return whenTrueType;
712
+ return whenTrueType ?? whenFalseType ?? 'interface{}';
713
+ })();
714
+ return `func() ${resultType} { if ${visit(node.condition)} { return ${whenTrue} }; return ${whenFalse} }()`;
715
+ }
716
+ function visitNullishCoalescingExpression(node) {
717
+ const leftType = inferExpressionType(node.left);
718
+ const rightType = inferExpressionType(node.right);
719
+ if (leftType && leftType.startsWith('*')) {
720
+ const leftValueType = leftType.slice(1);
721
+ const expectedType = inferExpectedTypeFromContext(node);
722
+ const resultType = expectedType || (rightType === leftValueType ? leftValueType : (rightType ?? leftType));
723
+ const tmp = getTempName('nullish');
724
+ const leftExpr = visit(node.left);
725
+ const rightExpr = visit(node.right);
726
+ const returnLeft = resultType === leftValueType ? `*${tmp}` : tmp;
727
+ return `func() ${resultType} { ${tmp} := ${leftExpr}; if ${tmp} == nil { return ${rightExpr} }; return ${returnLeft} }()`;
728
+ }
729
+ return visit(node.left);
730
+ }
731
+ function visitOptionalPropertyAccess(node) {
732
+ const baseExpr = visit(node.expression);
733
+ const baseType = inferExpressionType(node.expression);
734
+ if (!baseType || !baseType.startsWith('*')) {
735
+ const objectType = resolveExpressionType(node.expression);
736
+ return getAcessString(baseExpr, visit(node.name), objectType);
737
+ }
738
+ const className = baseType.replace(/^\*/, '').replace(/\[.*\]$/, '');
739
+ const propertyType = classPropertyTypes.get(className)?.get(node.name.text) ?? 'interface{}';
740
+ const nullableType = makeNullableType(propertyType);
741
+ const tmp = getTempName('opt');
742
+ const propertyAccess = `${tmp}.${visit(node.name)}`;
743
+ if (nullableType.startsWith('*') && nullableType.slice(1) === propertyType) {
744
+ const valueTemp = getTempName('optv');
745
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; ${valueTemp} := ${propertyAccess}; return &${valueTemp} }()`;
746
+ }
747
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${propertyAccess} }()`;
748
+ }
749
+ function visitOptionalElementAccess(node) {
750
+ const baseExpr = visit(node.expression);
751
+ const baseType = inferExpressionType(node.expression);
752
+ if (!baseType || !baseType.startsWith('*')) {
753
+ return `${baseExpr}[int(${visit(node.argumentExpression)})]`;
754
+ }
755
+ const valueType = inferExpectedTypeFromContext(node) ?? 'interface{}';
756
+ const nullableType = makeNullableType(valueType);
757
+ const tmp = getTempName('opte');
758
+ const elementExpr = `${tmp}[int(${visit(node.argumentExpression)})]`;
759
+ if (nullableType.startsWith('*') && nullableType.slice(1) === valueType) {
760
+ const valueTemp = getTempName('optev');
761
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; ${valueTemp} := ${elementExpr}; return &${valueTemp} }()`;
762
+ }
763
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${elementExpr} }()`;
764
+ }
765
+ function visitOptionalCall(node) {
766
+ if (!ts.isPropertyAccessExpression(node.expression)) {
767
+ return `${visit(node.expression)}(${node.arguments.map((a) => visit(a)).join(', ')})`;
768
+ }
769
+ const baseNode = node.expression.expression;
770
+ const methodName = node.expression.name.text;
771
+ const baseExpr = visit(baseNode);
772
+ const baseType = inferExpressionType(baseNode);
773
+ const args = node.arguments.map((a) => visit(a)).join(', ');
774
+ if (!baseType || !baseType.startsWith('*')) {
775
+ return `${baseExpr}.${methodName}(${args})`;
776
+ }
777
+ const className = baseType.replace(/^\*/, '').replace(/\[.*\]$/, '');
778
+ const returnType = classMethodReturnTypes.get(className)?.get(methodName) ?? 'interface{}';
779
+ const nullableType = makeNullableType(returnType);
780
+ const tmp = getTempName('optc');
781
+ const callExpr = `${tmp}.${methodName}(${args})`;
782
+ if (nullableType.startsWith('*') && nullableType.slice(1) === returnType) {
783
+ const valueTemp = getTempName('optcv');
784
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; ${valueTemp} := ${callExpr}; return &${valueTemp} }()`;
785
+ }
786
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${callExpr} }()`;
787
+ }
788
+ function isArrayLikeGoType(goType) {
789
+ return !!goType && goType.startsWith('[]');
790
+ }
791
+ function getArrayElementTypeFromGoType(goType) {
792
+ if (!goType.startsWith('[]'))
793
+ return 'interface{}';
794
+ const elementType = goType.slice(2);
795
+ return elementType || 'interface{}';
796
+ }
797
+ function inferArrayCallbackReturnType(callback, elementType, fallbackType) {
798
+ if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
799
+ if (callback.type) {
800
+ const explicitType = getType(callback.type);
801
+ return explicitType || fallbackType;
802
+ }
803
+ if (ts.isBlock(callback.body)) {
804
+ return fallbackType;
805
+ }
806
+ const inferred = inferExpressionType(callback.body);
807
+ return inferred ?? fallbackType;
808
+ }
809
+ if (ts.isIdentifier(callback)) {
810
+ const knownType = variableGoTypes.get(callback.text);
811
+ if (knownType)
812
+ return knownType;
813
+ }
814
+ return fallbackType;
815
+ }
816
+ function buildArrayCallbackInfo(callback, elementType, forcedReturnType) {
817
+ if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
818
+ const paramCount = callback.parameters.length;
819
+ const callbackReturnType = forcedReturnType ?? inferArrayCallbackReturnType(callback, elementType, 'interface{}');
820
+ const params = [];
821
+ if (paramCount > 0) {
822
+ params.push(`${visit(callback.parameters[0].name)} ${elementType}`);
823
+ }
824
+ if (paramCount > 1) {
825
+ params.push(`${visit(callback.parameters[1].name)} float64`);
826
+ }
827
+ if (paramCount > 2) {
828
+ params.push(`${visit(callback.parameters[2].name)} []${elementType}`);
829
+ }
830
+ const body = ts.isBlock(callback.body)
831
+ ? visit(callback.body)
832
+ : `{\n\t\treturn ${visit(callback.body)};\n\t}`;
833
+ return {
834
+ fnExpr: `func(${params.join(', ')}) ${callbackReturnType} ${body}`,
835
+ paramCount,
836
+ returnType: callbackReturnType
837
+ };
838
+ }
839
+ return {
840
+ fnExpr: visit(callback),
841
+ paramCount: 1,
842
+ returnType: forcedReturnType ?? 'interface{}'
843
+ };
844
+ }
845
+ function buildArrayCallbackInvocation(callbackInfo, itemVar, indexVar, arrayVar) {
846
+ const args = [];
847
+ if (callbackInfo.paramCount > 0)
848
+ args.push(itemVar);
849
+ if (callbackInfo.paramCount > 1)
850
+ args.push(`float64(${indexVar})`);
851
+ if (callbackInfo.paramCount > 2)
852
+ args.push(arrayVar);
853
+ return `(${callbackInfo.fnExpr})(${args.join(', ')})`;
854
+ }
855
+ function visitArrayHigherOrderCall(node) {
856
+ if (!ts.isPropertyAccessExpression(node.expression))
857
+ return undefined;
858
+ const methodName = node.expression.name.text;
859
+ if (!['map', 'filter', 'some', 'find', 'join'].includes(methodName)) {
860
+ return undefined;
861
+ }
862
+ const arrayExprNode = node.expression.expression;
863
+ const arrayExpr = visit(arrayExprNode);
864
+ const ownerType = inferExpressionType(arrayExprNode);
865
+ const elementType = isArrayLikeGoType(ownerType)
866
+ ? getArrayElementTypeFromGoType(ownerType)
867
+ : 'interface{}';
868
+ if (methodName === 'join') {
869
+ importedPackages.add('strings');
870
+ importedPackages.add('fmt');
871
+ const separator = node.arguments[0] ? visit(node.arguments[0]) : '""';
872
+ const arrVar = getTempName('arrjoin');
873
+ const partsVar = getTempName('parts');
874
+ 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}) }()`;
875
+ }
876
+ const callback = node.arguments[0];
877
+ if (!callback) {
878
+ return undefined;
879
+ }
880
+ const arrVar = getTempName('arrhof');
881
+ const idxVar = getTempName('i');
882
+ const itemVar = getTempName('item');
883
+ if (methodName === 'map') {
884
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, elementType);
885
+ const mappedType = callbackInfo.returnType || 'interface{}';
886
+ const resultVar = getTempName('mapres');
887
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
888
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
889
+ return `func() []${mappedType} { ${arrVar} := ${arrayExpr}; ${resultVar} := make([]${mappedType}, 0, len(${arrVar})); for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${resultVar} = append(${resultVar}, ${callbackCall}) }; return ${resultVar} }()`;
890
+ }
891
+ if (methodName === 'filter') {
892
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
893
+ const resultVar = getTempName('filterres');
894
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
895
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
896
+ return `func() []${elementType} { ${arrVar} := ${arrayExpr}; ${resultVar} := make([]${elementType}, 0, len(${arrVar})); for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { ${resultVar} = append(${resultVar}, ${itemVar}) } }; return ${resultVar} }()`;
897
+ }
898
+ if (methodName === 'some') {
899
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
900
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
901
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
902
+ return `func() bool { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return true } }; return false }()`;
903
+ }
904
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
905
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
906
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
907
+ return `func() ${elementType} { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return ${itemVar} } }; var __zero ${elementType}; return __zero }()`;
908
+ }
909
+ function getAliasType(name, seen = new Set()) {
910
+ const aliasType = typeAliases.get(name);
911
+ if (!aliasType)
912
+ return undefined;
913
+ if (seen.has(name))
914
+ return undefined;
915
+ if (ts.isTypeReferenceNode(aliasType) && ts.isIdentifier(aliasType.typeName)) {
916
+ const nestedName = aliasType.typeName.text;
917
+ if (typeAliases.has(nestedName)) {
918
+ seen.add(name);
919
+ return getAliasType(nestedName, seen) ?? aliasType;
920
+ }
921
+ }
922
+ return aliasType;
923
+ }
924
+ function getOptionalNodeType(typeNode, isOptional) {
925
+ const baseType = typeNode ? getType(typeNode) : 'interface{}';
926
+ if (!isOptional)
927
+ return baseType;
928
+ if (baseType === 'interface{}' || baseType.startsWith('*'))
929
+ return baseType;
930
+ if (['string', 'float64', 'bool'].includes(baseType))
931
+ return `*${baseType}`;
932
+ return baseType;
933
+ }
934
+ function getEnumMemberName(name) {
935
+ if (ts.isIdentifier(name)) {
936
+ return getSafeName(name.text);
937
+ }
938
+ if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
939
+ const sanitized = name.text.replace(/[^a-zA-Z0-9_]/g, '_');
940
+ return sanitized.length > 0 ? sanitized : 'Member';
941
+ }
942
+ return 'Member';
943
+ }
944
+ function getEnumBaseType(node) {
945
+ for (const member of node.members) {
946
+ const initializer = member.initializer;
947
+ if (!initializer)
948
+ continue;
949
+ if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {
950
+ return 'string';
951
+ }
952
+ }
953
+ return 'float64';
954
+ }
955
+ function readNumericEnumInitializer(initializer) {
956
+ if (ts.isNumericLiteral(initializer)) {
957
+ return Number(initializer.text);
958
+ }
959
+ if (ts.isPrefixUnaryExpression(initializer) &&
960
+ initializer.operator === ts.SyntaxKind.MinusToken &&
961
+ ts.isNumericLiteral(initializer.operand)) {
962
+ return -Number(initializer.operand.text);
963
+ }
964
+ return undefined;
965
+ }
966
+ function visitEnumDeclaration(node) {
967
+ const enumName = getSafeName(node.name.text);
968
+ const baseType = enumBaseTypes.get(node.name.text) ?? getEnumBaseType(node);
969
+ let nextNumericValue = 0;
970
+ let canAutoIncrement = true;
971
+ const members = [];
972
+ for (const member of node.members) {
973
+ const memberName = getEnumMemberName(member.name);
974
+ const symbolName = `${enumName}_${memberName}`;
975
+ let valueExpr;
976
+ if (member.initializer) {
977
+ if (baseType === 'float64') {
978
+ const numericValue = readNumericEnumInitializer(member.initializer);
979
+ if (numericValue !== undefined) {
980
+ valueExpr = `${numericValue}`;
981
+ nextNumericValue = numericValue + 1;
982
+ canAutoIncrement = true;
983
+ }
984
+ else {
985
+ valueExpr = `float64(${visit(member.initializer)})`;
986
+ canAutoIncrement = false;
987
+ }
988
+ }
989
+ else {
990
+ valueExpr = visit(member.initializer);
991
+ }
992
+ }
993
+ else if (baseType === 'float64') {
994
+ const currentValue = canAutoIncrement ? nextNumericValue : 0;
995
+ valueExpr = `${currentValue}`;
996
+ nextNumericValue = currentValue + 1;
997
+ }
998
+ else {
999
+ valueExpr = toGoStringLiteral(memberName);
1000
+ }
1001
+ members.push(`\t${symbolName} ${enumName} = ${enumName}(${valueExpr})`);
1002
+ }
1003
+ return `type ${enumName} ${baseType}\n\nvar (\n${members.join('\n')}\n)`;
1004
+ }
382
1005
  function getType(typeNode, getArrayType = false) {
383
1006
  if (!typeNode)
384
1007
  return ':';
1008
+ if (ts.isArrayTypeNode(typeNode)) {
1009
+ const elementType = getType(typeNode.elementType);
1010
+ return getArrayType ? elementType : `[]${elementType}`;
1011
+ }
385
1012
  // Handle union types (e.g. string | null, number | undefined)
386
1013
  if (ts.isUnionTypeNode(typeNode)) {
387
1014
  const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
@@ -401,9 +1028,19 @@ function getType(typeNode, getArrayType = false) {
401
1028
  }
402
1029
  if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
403
1030
  const name = typeNode.typeName.text;
1031
+ if (enumNames.has(name)) {
1032
+ return getSafeName(name);
1033
+ }
1034
+ const aliasType = getAliasType(name);
1035
+ if (aliasType) {
1036
+ return getType(aliasType, getArrayType);
1037
+ }
404
1038
  if (name === 'Promise' && typeNode.typeArguments && typeNode.typeArguments.length > 0) {
405
1039
  return `chan ${getType(typeNode.typeArguments[0])}`;
406
1040
  }
1041
+ if (name === 'RegExp') {
1042
+ return '*regexp.Regexp';
1043
+ }
407
1044
  const typeArgs = getTypeArguments(typeNode.typeArguments);
408
1045
  if (classNames.has(name)) {
409
1046
  return `*${name}${typeArgs}`;
@@ -460,6 +1097,20 @@ function getTypeCategory(typeNode) {
460
1097
  }
461
1098
  return undefined;
462
1099
  }
1100
+ function getClassNameFromTypeNode(typeNode) {
1101
+ if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1102
+ return classNames.has(typeNode.typeName.text) ? typeNode.typeName.text : undefined;
1103
+ }
1104
+ if (ts.isUnionTypeNode(typeNode)) {
1105
+ const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1106
+ t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1107
+ !(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
1108
+ if (nonNullTypes.length === 1) {
1109
+ return getClassNameFromTypeNode(nonNullTypes[0]);
1110
+ }
1111
+ }
1112
+ return undefined;
1113
+ }
463
1114
  function resolveExpressionType(expr) {
464
1115
  if (ts.isIdentifier(expr)) {
465
1116
  return variableTypes.get(expr.text);
@@ -615,6 +1266,10 @@ const stringMethodHandlers = {
615
1266
  return `fmt.Sprintf("%v", ${obj})`;
616
1267
  }
617
1268
  };
1269
+ const regexpMethodHandlers = {
1270
+ test: (obj, args) => `${obj}.MatchString(${args[0]})`,
1271
+ exec: (obj, args) => `${obj}.FindStringSubmatch(${args[0]})`
1272
+ };
618
1273
  const arrayMethodHandlers = {
619
1274
  push: (obj, args) => `${obj} = append(${obj}, ${args.join(', ')})`,
620
1275
  join: (obj, args) => {
@@ -638,6 +1293,14 @@ function getDynamicCallHandler(caller, objectType) {
638
1293
  const dotIndex = caller.lastIndexOf('.');
639
1294
  if (dotIndex !== -1) {
640
1295
  const methodName = caller.substring(dotIndex + 1);
1296
+ // toString() is universal — works for any type including numbers and objects
1297
+ if (methodName === 'toString') {
1298
+ return (c) => {
1299
+ const obj = c.substring(0, dotIndex);
1300
+ importedPackages.add('fmt');
1301
+ return `fmt.Sprintf("%v", ${obj})`;
1302
+ };
1303
+ }
641
1304
  // Class instances use their own methods — never intercept
642
1305
  if (objectType === 'class')
643
1306
  return null;
@@ -648,9 +1311,15 @@ function getDynamicCallHandler(caller, objectType) {
648
1311
  else if (objectType === 'array') {
649
1312
  handler = arrayMethodHandlers[methodName];
650
1313
  }
1314
+ else if (objectType === 'RegExp') {
1315
+ handler = regexpMethodHandlers[methodName];
1316
+ }
651
1317
  else {
652
1318
  // Unknown type: try both maps for backward compatibility
653
- handler = stringMethodHandlers[methodName] ?? arrayMethodHandlers[methodName];
1319
+ handler =
1320
+ stringMethodHandlers[methodName] ??
1321
+ arrayMethodHandlers[methodName] ??
1322
+ regexpMethodHandlers[methodName];
654
1323
  }
655
1324
  if (handler) {
656
1325
  return (c, args) => {
@@ -690,6 +1359,16 @@ function getOperatorText(operator) {
690
1359
  function getTimerName(name) {
691
1360
  return `__timer_${name.replaceAll(' ', '_').replaceAll('"', '')}__`;
692
1361
  }
1362
+ function jsRegexFlagsToGo(flags) {
1363
+ let goFlags = '';
1364
+ if (flags.includes('i'))
1365
+ goFlags += 'i';
1366
+ if (flags.includes('m'))
1367
+ goFlags += 'm';
1368
+ if (flags.includes('s'))
1369
+ goFlags += 's';
1370
+ return goFlags ? `(?${goFlags})` : '';
1371
+ }
693
1372
  function getTypeParameters(typeParameters) {
694
1373
  if (!typeParameters || typeParameters.length === 0)
695
1374
  return '';
@@ -712,6 +1391,59 @@ function getTypeArguments(typeArguments) {
712
1391
  const args = typeArguments.map((ta) => getType(ta));
713
1392
  return `[${args.join(', ')}]`;
714
1393
  }
1394
+ function getParameterGoType(param) {
1395
+ if (param.type) {
1396
+ const explicitType = getType(param.type);
1397
+ return explicitType === ':' ? 'interface{}' : explicitType;
1398
+ }
1399
+ if (param.initializer) {
1400
+ const inferredType = inferExpressionType(param.initializer);
1401
+ if (inferredType && inferredType !== 'nil' && inferredType !== ':') {
1402
+ return inferredType;
1403
+ }
1404
+ }
1405
+ return 'interface{}';
1406
+ }
1407
+ function getFunctionParametersInfo(parameters) {
1408
+ if (parameters.length === 0) {
1409
+ return { signature: '', prefixBlockContent: '' };
1410
+ }
1411
+ const firstDefaultIndex = parameters.findIndex((p) => !!p.initializer);
1412
+ if (firstDefaultIndex === -1) {
1413
+ return {
1414
+ signature: parameters.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`).join(', '),
1415
+ prefixBlockContent: ''
1416
+ };
1417
+ }
1418
+ const hasRequiredAfterDefault = parameters
1419
+ .slice(firstDefaultIndex)
1420
+ .some((p) => !p.initializer);
1421
+ if (hasRequiredAfterDefault) {
1422
+ return {
1423
+ signature: parameters.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`).join(', '),
1424
+ prefixBlockContent: ''
1425
+ };
1426
+ }
1427
+ const requiredParams = parameters.slice(0, firstDefaultIndex);
1428
+ const defaultedParams = parameters.slice(firstDefaultIndex);
1429
+ const signatureParts = requiredParams.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`);
1430
+ signatureParts.push('__defaultArgs ...interface{}');
1431
+ const prefixBlockContent = defaultedParams
1432
+ .map((param, index) => {
1433
+ const paramName = visit(param.name);
1434
+ const paramType = getParameterGoType(param);
1435
+ const defaultValue = visit(param.initializer);
1436
+ if (paramType === 'interface{}') {
1437
+ return `var ${paramName} interface{}\n\t\tif len(__defaultArgs) > ${index} {\n\t\t\t${paramName} = __defaultArgs[${index}]\n\t\t} else {\n\t\t\t${paramName} = ${defaultValue}\n\t\t}\n\t\t`;
1438
+ }
1439
+ return `var ${paramName} ${paramType}\n\t\tif len(__defaultArgs) > ${index} {\n\t\t\t${paramName} = __defaultArgs[${index}].(${paramType})\n\t\t} else {\n\t\t\t${paramName} = ${defaultValue}\n\t\t}\n\t\t`;
1440
+ })
1441
+ .join('');
1442
+ return {
1443
+ signature: signatureParts.join(', '),
1444
+ prefixBlockContent
1445
+ };
1446
+ }
715
1447
  function getSafeName(name) {
716
1448
  if (!dangerousNames.has(name)) {
717
1449
  return name;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typenative",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Build native applications using Typescript.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,7 +25,13 @@
25
25
  "test11": "node ./bin/index --source test/test11.spec.ts --script",
26
26
  "test12": "node ./bin/index --source test/test12.spec.ts --script",
27
27
  "test13": "node ./bin/index --source test/test13.spec.ts --script",
28
- "test14": "node ./bin/index --source test/test14.spec.ts --script"
28
+ "test14": "node ./bin/index --source test/test14.spec.ts --script",
29
+ "test15": "node ./bin/index --source test/test15.spec.ts --script",
30
+ "test16": "node ./bin/index --source test/test16.spec.ts --script",
31
+ "test17": "node ./bin/index --source test/test17.spec.ts --script",
32
+ "test18": "node ./bin/index --source test/test18.spec.ts --script",
33
+ "test19": "node ./bin/index --source test/test19.spec.ts --script",
34
+ "test20": "node ./bin/index --source test/Test20.spec.ts --script"
29
35
  },
30
36
  "repository": {
31
37
  "type": "git",
@@ -3,15 +3,16 @@ interface Boolean {}
3
3
  interface CallableFunction extends Function {}
4
4
 
5
5
  interface Function {
6
- apply(thisArg: any, argArray?: any): any;
6
+ /* apply(thisArg: any, argArray?: any): any;
7
7
  call(thisArg: any, ...argArray: any[]): any;
8
- bind(thisArg: any, ...argArray: any[]): any;
8
+ bind(thisArg: any, ...argArray: any[]): any; */
9
9
  }
10
10
 
11
11
  interface IArguments {
12
12
  [index: number]: any;
13
13
  length: number;
14
14
  }
15
+
15
16
  interface NewableFunction extends Function {}
16
17
 
17
18
  interface Number {
@@ -28,6 +29,12 @@ interface RegExp {
28
29
  exec(string: string): string[] | null;
29
30
  }
30
31
 
32
+ interface RegExpConstructor {
33
+ new (pattern: string, flags?: string): RegExp;
34
+ }
35
+
36
+ declare var RegExp: RegExpConstructor;
37
+
31
38
  interface SymbolConstructor {
32
39
  readonly iterator: unique symbol;
33
40
  }
@@ -74,6 +81,22 @@ interface Array<T> extends IterableIterator<T> {
74
81
  * Returns a copy of a section of an array.
75
82
  */
76
83
  slice(start?: number, end?: number): T[];
84
+ /**
85
+ * Calls a defined callback function on each element of an array, and returns an array that contains the results.
86
+ */
87
+ map<U>(callback: (value: T, index?: number, array?: T[]) => U): U[];
88
+ /**
89
+ * Returns the elements of an array that meet the condition specified in a callback function.
90
+ */
91
+ filter(callback: (value: T, index?: number, array?: T[]) => boolean): T[];
92
+ /**
93
+ * Determines whether the specified callback function returns true for any element of an array.
94
+ */
95
+ some(callback: (value: T, index?: number, array?: T[]) => boolean): boolean;
96
+ /**
97
+ * Returns the value of the first element in the array where callback is true, and undefined otherwise.
98
+ */
99
+ find(callback: (value: T, index?: number, array?: T[]) => boolean): T | undefined;
77
100
  /**
78
101
  * Returns the index of the first occurrence of a value in an array, or -1 if it is not present.
79
102
  */