goscript 0.0.61 → 0.0.62
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/README.md +9 -0
- package/compiler/analysis.go +536 -15
- package/compiler/assignment.go +72 -0
- package/compiler/compiler.go +64 -11
- package/compiler/composite-lit.go +29 -8
- package/compiler/decl.go +20 -11
- package/compiler/expr-call-async.go +26 -1
- package/compiler/expr-call-builtins.go +60 -4
- package/compiler/expr-call-type-conversion.go +37 -5
- package/compiler/expr-call.go +16 -3
- package/compiler/expr-selector.go +35 -2
- package/compiler/expr-type.go +12 -2
- package/compiler/expr.go +37 -0
- package/compiler/index.test.ts +3 -1
- package/compiler/lit.go +13 -4
- package/compiler/spec-struct.go +30 -8
- package/compiler/spec-value.go +2 -2
- package/compiler/spec.go +21 -4
- package/compiler/stmt-assign.go +71 -0
- package/compiler/stmt-range.go +2 -2
- package/compiler/stmt.go +128 -0
- package/compiler/type-utils.go +40 -1
- package/compiler/type.go +50 -12
- package/dist/gs/builtin/builtin.d.ts +8 -1
- package/dist/gs/builtin/builtin.js +26 -1
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/errors.d.ts +1 -0
- package/dist/gs/builtin/errors.js +8 -0
- package/dist/gs/builtin/errors.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +5 -4
- package/dist/gs/builtin/slice.js +45 -14
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +23 -2
- package/dist/gs/builtin/type.js +125 -0
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/bytes/reader.gs.d.ts +1 -1
- package/dist/gs/bytes/reader.gs.js +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +2 -2
- package/dist/gs/reflect/index.js +1 -1
- package/dist/gs/reflect/index.js.map +1 -1
- package/dist/gs/reflect/map.d.ts +3 -2
- package/dist/gs/reflect/map.js +37 -3
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +50 -12
- package/dist/gs/reflect/type.js +820 -27
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.d.ts +11 -12
- package/dist/gs/reflect/types.js +26 -15
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/value.d.ts +4 -4
- package/dist/gs/reflect/value.js +8 -2
- package/dist/gs/reflect/value.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +21 -0
- package/dist/gs/slices/slices.js +48 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +2 -2
- package/dist/gs/sync/atomic/type.gs.js +12 -2
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/unicode/utf8/utf8.d.ts +2 -2
- package/dist/gs/unicode/utf8/utf8.js +10 -6
- package/dist/gs/unicode/utf8/utf8.js.map +1 -1
- package/go.mod +4 -4
- package/go.sum +8 -8
- package/gs/builtin/builtin.ts +27 -2
- package/gs/builtin/errors.ts +12 -0
- package/gs/builtin/slice.ts +71 -7
- package/gs/builtin/type.ts +159 -2
- package/gs/bytes/reader.gs.ts +2 -2
- package/gs/math/hypot.gs.test.ts +3 -1
- package/gs/math/pow10.gs.test.ts +5 -4
- package/gs/reflect/index.ts +3 -2
- package/gs/reflect/map.test.ts +7 -6
- package/gs/reflect/map.ts +49 -7
- package/gs/reflect/type.ts +1053 -54
- package/gs/reflect/types.ts +34 -21
- package/gs/reflect/value.ts +12 -6
- package/gs/slices/slices.ts +55 -0
- package/gs/sync/atomic/type.gs.ts +14 -5
- package/gs/unicode/utf8/utf8.ts +12 -8
- package/package.json +13 -13
|
@@ -130,10 +130,23 @@ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
|
|
|
130
130
|
|
|
131
131
|
// Fallback / Normal Case (e.g., obj.Field, pkg.Var, method calls)
|
|
132
132
|
// WriteValueExpr handles adding .value for the base variable itself if it's varrefed.
|
|
133
|
+
|
|
134
|
+
// Check if the base expression is an async call - if so, we need to wrap it in parentheses
|
|
135
|
+
// so that await binds correctly: (await asyncFn()).method instead of await asyncFn().method
|
|
136
|
+
needsParensForAsync := false
|
|
137
|
+
if callExpr, isCall := exp.X.(*ast.CallExpr); isCall && c.isCallExprAsync(callExpr) {
|
|
138
|
+
needsParensForAsync = true
|
|
139
|
+
c.tsw.WriteLiterally("(")
|
|
140
|
+
}
|
|
141
|
+
|
|
133
142
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
134
143
|
return fmt.Errorf("failed to write selector base expression: %w", err)
|
|
135
144
|
}
|
|
136
145
|
|
|
146
|
+
if needsParensForAsync {
|
|
147
|
+
c.tsw.WriteLiterally(")")
|
|
148
|
+
}
|
|
149
|
+
|
|
137
150
|
// Add null assertion for selector expressions when accessing fields/methods on nullable types
|
|
138
151
|
// In Go, accessing fields or calling methods on nil pointers/interfaces panics, so we should throw in TypeScript
|
|
139
152
|
baseType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
@@ -212,8 +225,24 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
|
|
|
212
225
|
return fmt.Errorf("failed to get qualified type name for primitive receiver")
|
|
213
226
|
}
|
|
214
227
|
|
|
215
|
-
//
|
|
216
|
-
|
|
228
|
+
// Get the method parameters to forward them
|
|
229
|
+
params := sig.Params()
|
|
230
|
+
paramNames := make([]string, params.Len())
|
|
231
|
+
for i := 0; i < params.Len(); i++ {
|
|
232
|
+
paramNames[i] = fmt.Sprintf("_p%d", i)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Generate: ((_p0: type0, _p1: type1, ...) => TypeName_MethodName(receiverValue, _p0, _p1, ...))
|
|
236
|
+
c.tsw.WriteLiterally("((")
|
|
237
|
+
for i, name := range paramNames {
|
|
238
|
+
if i > 0 {
|
|
239
|
+
c.tsw.WriteLiterally(", ")
|
|
240
|
+
}
|
|
241
|
+
c.tsw.WriteLiterally(name)
|
|
242
|
+
c.tsw.WriteLiterally(": ")
|
|
243
|
+
c.WriteGoType(params.At(i).Type(), GoTypeContextGeneral)
|
|
244
|
+
}
|
|
245
|
+
c.tsw.WriteLiterally(") => ")
|
|
217
246
|
c.tsw.WriteLiterally(typeName)
|
|
218
247
|
c.tsw.WriteLiterally("_")
|
|
219
248
|
c.tsw.WriteLiterally(exp.Sel.Name)
|
|
@@ -221,6 +250,10 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
|
|
|
221
250
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
222
251
|
return fmt.Errorf("failed to write method value receiver: %w", err)
|
|
223
252
|
}
|
|
253
|
+
for _, name := range paramNames {
|
|
254
|
+
c.tsw.WriteLiterally(", ")
|
|
255
|
+
c.tsw.WriteLiterally(name)
|
|
256
|
+
}
|
|
224
257
|
c.tsw.WriteLiterally("))")
|
|
225
258
|
|
|
226
259
|
return nil
|
package/compiler/expr-type.go
CHANGED
|
@@ -26,13 +26,23 @@ func (c *GoToTSCompiler) WriteTypeExpr(a ast.Expr) {
|
|
|
26
26
|
// Check if this is a package selector (e.g., os.FileInfo)
|
|
27
27
|
if obj := c.pkg.TypesInfo.Uses[pkgIdent]; obj != nil {
|
|
28
28
|
if _, isPkg := obj.(*types.PkgName); isPkg {
|
|
29
|
-
// This is a package.Type reference
|
|
29
|
+
// This is a package.Type reference
|
|
30
|
+
typ := c.pkg.TypesInfo.TypeOf(a)
|
|
31
|
+
|
|
32
|
+
// Check if this is an interface type and add null | prefix
|
|
33
|
+
if typ != nil {
|
|
34
|
+
if _, isInterface := typ.Underlying().(*types.Interface); isInterface {
|
|
35
|
+
c.tsw.WriteLiterally("null | ")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Write the qualified name
|
|
30
40
|
c.tsw.WriteLiterally(pkgIdent.Name)
|
|
31
41
|
c.tsw.WriteLiterally(".")
|
|
32
42
|
c.tsw.WriteLiterally(selectorExpr.Sel.Name)
|
|
33
43
|
|
|
34
44
|
// Check if this is a function type and add | null
|
|
35
|
-
if typ
|
|
45
|
+
if typ != nil {
|
|
36
46
|
if namedType, isNamed := typ.(*types.Named); isNamed {
|
|
37
47
|
if _, isSignature := namedType.Underlying().(*types.Signature); isSignature {
|
|
38
48
|
c.tsw.WriteLiterally(" | null")
|
package/compiler/expr.go
CHANGED
|
@@ -441,9 +441,46 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
441
441
|
c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
// Check if this is a comparison that might trigger TypeScript's control flow narrowing.
|
|
445
|
+
// When a numeric field is compared to a literal, TypeScript may narrow the field type
|
|
446
|
+
// to that literal, causing subsequent comparisons to appear unintentional.
|
|
447
|
+
// We cast to number to widen the type and avoid these false positives.
|
|
448
|
+
needsNumberCast := false
|
|
449
|
+
if (exp.Op == token.EQL || exp.Op == token.NEQ) && c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
450
|
+
// Check if left side is a selector expression (field access) and right is a literal or constant
|
|
451
|
+
_, leftIsSelector := exp.X.(*ast.SelectorExpr)
|
|
452
|
+
_, rightIsLiteral := exp.Y.(*ast.BasicLit)
|
|
453
|
+
// Also check if right side is an identifier referring to a constant
|
|
454
|
+
rightIsConstant := false
|
|
455
|
+
if rightIdent, ok := exp.Y.(*ast.Ident); ok {
|
|
456
|
+
if obj := c.pkg.TypesInfo.Uses[rightIdent]; obj != nil {
|
|
457
|
+
if _, isConst := obj.(*types.Const); isConst {
|
|
458
|
+
rightIsConstant = true
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if leftIsSelector && (rightIsLiteral || rightIsConstant) {
|
|
463
|
+
leftType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
464
|
+
if leftType != nil {
|
|
465
|
+
if basic, ok := leftType.Underlying().(*types.Basic); ok {
|
|
466
|
+
// Check if it's a numeric type
|
|
467
|
+
if basic.Info()&types.IsNumeric != 0 {
|
|
468
|
+
needsNumberCast = true
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if needsNumberCast {
|
|
476
|
+
c.tsw.WriteLiterally("Number(")
|
|
477
|
+
}
|
|
444
478
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
445
479
|
return fmt.Errorf("failed to write binary expression left operand: %w", err)
|
|
446
480
|
}
|
|
481
|
+
if needsNumberCast {
|
|
482
|
+
c.tsw.WriteLiterally(")")
|
|
483
|
+
}
|
|
447
484
|
|
|
448
485
|
c.tsw.WriteLiterally(" ")
|
|
449
486
|
tokStr, ok := TokenToTs(exp.Op)
|
package/compiler/index.test.ts
CHANGED
|
@@ -24,6 +24,8 @@ describe("GoScript Compiler API", () => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
await expect(compile(config)).resolves.toBeUndefined();
|
|
27
|
-
|
|
27
|
+
// fs.access resolves to undefined/null on success - verify file exists
|
|
28
|
+
const fileExists = await fs.access(expectedOutputFile).then(() => true).catch(() => false);
|
|
29
|
+
expect(fileExists).toBe(true);
|
|
28
30
|
}, 30000); // 30 second timeout for compilation
|
|
29
31
|
});
|
package/compiler/lit.go
CHANGED
|
@@ -143,11 +143,20 @@ func (c *GoToTSCompiler) WriteFuncLitValue(exp *ast.FuncLit) error {
|
|
|
143
143
|
c.WriteTypeExpr(exp.Type.Results.List[0].Type)
|
|
144
144
|
} else {
|
|
145
145
|
c.tsw.WriteLiterally("[")
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
first := true
|
|
147
|
+
for _, field := range exp.Type.Results.List {
|
|
148
|
+
// Each field may represent multiple return values (e.g., "a, b int")
|
|
149
|
+
count := len(field.Names)
|
|
150
|
+
if count == 0 {
|
|
151
|
+
count = 1 // Unnamed return value
|
|
152
|
+
}
|
|
153
|
+
for j := 0; j < count; j++ {
|
|
154
|
+
if !first {
|
|
155
|
+
c.tsw.WriteLiterally(", ")
|
|
156
|
+
}
|
|
157
|
+
first = false
|
|
158
|
+
c.WriteTypeExpr(field.Type)
|
|
149
159
|
}
|
|
150
|
-
c.WriteTypeExpr(field.Type)
|
|
151
160
|
}
|
|
152
161
|
c.tsw.WriteLiterally("]")
|
|
153
162
|
}
|
package/compiler/spec-struct.go
CHANGED
|
@@ -4,7 +4,7 @@ import (
|
|
|
4
4
|
"fmt"
|
|
5
5
|
"go/ast"
|
|
6
6
|
"go/types"
|
|
7
|
-
"
|
|
7
|
+
"slices"
|
|
8
8
|
"strings"
|
|
9
9
|
)
|
|
10
10
|
|
|
@@ -109,8 +109,10 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Use AST-based type string when available, fall back to types-based
|
|
112
|
+
// Note: getASTTypeString already includes "null |" for interface types
|
|
112
113
|
astType := fieldASTTypes[fieldKeyName]
|
|
113
114
|
fieldTsType := c.getASTTypeString(astType, field.Type())
|
|
115
|
+
|
|
114
116
|
c.tsw.WriteLinef("%s: $.VarRef<%s>;", fieldKeyName, fieldTsType)
|
|
115
117
|
}
|
|
116
118
|
c.tsw.Indent(-1)
|
|
@@ -346,7 +348,16 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
346
348
|
}
|
|
347
349
|
|
|
348
350
|
// Promoted methods
|
|
349
|
-
|
|
351
|
+
// Use pointer to embedded type to get both value and pointer receiver methods
|
|
352
|
+
// This matches Go's behavior where embedding T promotes both T and *T methods
|
|
353
|
+
// Exception: For interfaces, use the interface directly as pointer-to-interface has no methods
|
|
354
|
+
methodSetType := embeddedFieldType
|
|
355
|
+
if _, isPtr := embeddedFieldType.(*types.Pointer); !isPtr {
|
|
356
|
+
if _, isInterface := embeddedFieldType.Underlying().(*types.Interface); !isInterface {
|
|
357
|
+
methodSetType = types.NewPointer(embeddedFieldType)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
embeddedMethodSet := types.NewMethodSet(methodSetType)
|
|
350
361
|
for k := range embeddedMethodSet.Len() {
|
|
351
362
|
methodSelection := embeddedMethodSet.At(k)
|
|
352
363
|
method := methodSelection.Obj().(*types.Func)
|
|
@@ -479,13 +490,23 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
479
490
|
if fieldKeyName == "_" {
|
|
480
491
|
continue
|
|
481
492
|
}
|
|
482
|
-
// fieldTsType := c.getTypeString(field.Type())
|
|
483
493
|
if !firstField {
|
|
484
494
|
c.tsw.WriteLiterally(", ")
|
|
485
495
|
}
|
|
486
496
|
firstField = false
|
|
487
497
|
c.tsw.WriteLiterallyf("%q: ", fieldKeyName)
|
|
488
|
-
|
|
498
|
+
|
|
499
|
+
// Get the struct tag for this field
|
|
500
|
+
tag := underlyingStruct.Tag(i)
|
|
501
|
+
if tag != "" {
|
|
502
|
+
// Write field info with tag as StructFieldInfo object
|
|
503
|
+
c.tsw.WriteLiterally("{ type: ")
|
|
504
|
+
c.writeTypeInfoObject(field.Type())
|
|
505
|
+
c.tsw.WriteLiterallyf(", tag: %q }", tag)
|
|
506
|
+
} else {
|
|
507
|
+
// No tag, write type info directly for backwards compatibility
|
|
508
|
+
c.writeTypeInfoObject(field.Type())
|
|
509
|
+
}
|
|
489
510
|
}
|
|
490
511
|
c.tsw.WriteLiterally("}")
|
|
491
512
|
c.tsw.WriteLine("")
|
|
@@ -543,15 +564,16 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
|
|
|
543
564
|
fieldType = ptr.Elem()
|
|
544
565
|
}
|
|
545
566
|
|
|
546
|
-
if
|
|
547
|
-
embeddedName := named.Obj().Name()
|
|
567
|
+
if _, ok := fieldType.(*types.Named); ok {
|
|
548
568
|
// Check if the embedded type is an interface
|
|
549
569
|
if _, isInterface := fieldType.Underlying().(*types.Interface); isInterface {
|
|
550
570
|
// For embedded interfaces, use the full qualified interface type
|
|
551
571
|
embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = c.getTypeString(field.Type())
|
|
552
572
|
} else {
|
|
553
573
|
// For embedded structs, use ConstructorParameters for field-based initialization
|
|
554
|
-
|
|
574
|
+
// Use getTypeString to get the qualified type name (e.g., bytes.Buffer not just Buffer)
|
|
575
|
+
qualifiedTypeName := c.getTypeString(fieldType)
|
|
576
|
+
embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = fmt.Sprintf("Partial<ConstructorParameters<typeof %s>[0]>", qualifiedTypeName)
|
|
555
577
|
}
|
|
556
578
|
}
|
|
557
579
|
continue
|
|
@@ -573,7 +595,7 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
|
|
|
573
595
|
for name := range fieldMap {
|
|
574
596
|
fieldNames = append(fieldNames, name)
|
|
575
597
|
}
|
|
576
|
-
|
|
598
|
+
slices.Sort(fieldNames)
|
|
577
599
|
|
|
578
600
|
var fieldDefs []string
|
|
579
601
|
for _, fieldName := range fieldNames {
|
package/compiler/spec-value.go
CHANGED
|
@@ -164,7 +164,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
|
|
|
164
164
|
isInsideFunction = nodeInfo.IsInsideFunction
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
if
|
|
167
|
+
if !isInsideFunction {
|
|
168
168
|
c.tsw.WriteLiterally("export ")
|
|
169
169
|
}
|
|
170
170
|
c.tsw.WriteLiterally("let ")
|
|
@@ -493,7 +493,7 @@ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
|
|
|
493
493
|
isInsideFunction = nodeInfo.IsInsideFunction
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
-
if
|
|
496
|
+
if !isInsideFunction {
|
|
497
497
|
c.tsw.WriteLiterally("export ")
|
|
498
498
|
}
|
|
499
499
|
|
package/compiler/spec.go
CHANGED
|
@@ -57,6 +57,7 @@ func (c *GoToTSCompiler) getEmbeddedFieldKeyName(fieldType types.Type) string {
|
|
|
57
57
|
|
|
58
58
|
func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Type, doc, comment *ast.CommentGroup, astType ast.Expr) {
|
|
59
59
|
// Use AST type information if available to preserve qualified names
|
|
60
|
+
// Note: getASTTypeString/getTypeString already includes "null |" for interface types
|
|
60
61
|
var fieldTypeStr string
|
|
61
62
|
if astType != nil {
|
|
62
63
|
fieldTypeStr = c.getASTTypeString(astType, fieldType)
|
|
@@ -406,11 +407,20 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
|
|
|
406
407
|
c.WriteTypeExpr(funcDecl.Type.Results.List[0].Type)
|
|
407
408
|
} else {
|
|
408
409
|
c.tsw.WriteLiterally("[")
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
first := true
|
|
411
|
+
for _, field := range funcDecl.Type.Results.List {
|
|
412
|
+
// Each field may represent multiple return values (e.g., "a, b int")
|
|
413
|
+
count := len(field.Names)
|
|
414
|
+
if count == 0 {
|
|
415
|
+
count = 1 // Unnamed return value
|
|
416
|
+
}
|
|
417
|
+
for j := 0; j < count; j++ {
|
|
418
|
+
if !first {
|
|
419
|
+
c.tsw.WriteLiterally(", ")
|
|
420
|
+
}
|
|
421
|
+
first = false
|
|
422
|
+
c.WriteTypeExpr(field.Type)
|
|
412
423
|
}
|
|
413
|
-
c.WriteTypeExpr(field.Type)
|
|
414
424
|
}
|
|
415
425
|
c.tsw.WriteLiterally("]")
|
|
416
426
|
}
|
|
@@ -625,6 +635,13 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
|
625
635
|
importVars: make(map[string]struct{}),
|
|
626
636
|
}
|
|
627
637
|
|
|
638
|
+
// Skip writing the import if it was already written as a synthetic import
|
|
639
|
+
// This prevents duplicate imports when a file needs an import both from
|
|
640
|
+
// its AST and for promoted methods from embedded structs
|
|
641
|
+
if _, isSynthetic := c.analysis.SyntheticImports[impName]; isSynthetic {
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
|
|
628
645
|
c.tsw.WriteImport(impName, tsImportPath+"/index.js")
|
|
629
646
|
}
|
|
630
647
|
|
package/compiler/stmt-assign.go
CHANGED
|
@@ -168,6 +168,21 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
168
168
|
|
|
169
169
|
// Handle single assignment using writeAssignmentCore
|
|
170
170
|
if len(exp.Lhs) == 1 {
|
|
171
|
+
// Check for type shadowing (e.g., field := field{...})
|
|
172
|
+
// In this case, we need to rename the variable to avoid TypeScript shadowing
|
|
173
|
+
if nodeInfo := c.analysis.NodeData[exp]; nodeInfo != nil && nodeInfo.ShadowingInfo != nil {
|
|
174
|
+
if lhsIdent, ok := exp.Lhs[0].(*ast.Ident); ok && lhsIdent.Name != "_" {
|
|
175
|
+
if renamedVar, hasTypeShadow := nodeInfo.ShadowingInfo.TypeShadowedVars[lhsIdent.Name]; hasTypeShadow {
|
|
176
|
+
if err := c.writeTypeShadowedAssignment(exp, lhsIdent.Name, renamedVar); err != nil {
|
|
177
|
+
return err
|
|
178
|
+
}
|
|
179
|
+
c.writeInlineComment(exp)
|
|
180
|
+
c.tsw.WriteLine("")
|
|
181
|
+
return nil
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
171
186
|
addDeclaration := exp.Tok == token.DEFINE
|
|
172
187
|
if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
|
|
173
188
|
return err
|
|
@@ -536,3 +551,59 @@ func (c *GoToTSCompiler) writeMapLookupWithExists(lhs []ast.Expr, indexExpr *ast
|
|
|
536
551
|
|
|
537
552
|
return nil
|
|
538
553
|
}
|
|
554
|
+
|
|
555
|
+
// writeTypeShadowedAssignment handles the case where a variable name shadows a type name
|
|
556
|
+
// used in its initialization (e.g., field := field{...}).
|
|
557
|
+
// In TypeScript, `let field = new field({...})` fails because the variable shadows the class
|
|
558
|
+
// before initialization due to the Temporal Dead Zone (TDZ). The TDZ extends from the
|
|
559
|
+
// start of the block scope to the point of initialization, so even capturing the type
|
|
560
|
+
// reference before the `let` declaration doesn't work - they're in the same block.
|
|
561
|
+
//
|
|
562
|
+
// We solve this by renaming the variable to avoid the conflict entirely:
|
|
563
|
+
//
|
|
564
|
+
// let field_ = $.markAsStructValue(new field({...}));
|
|
565
|
+
//
|
|
566
|
+
// Then we need to track that all subsequent references to `field` should use `field_`.
|
|
567
|
+
// This is stored in the analysis NodeInfo.IdentifierMapping.
|
|
568
|
+
func (c *GoToTSCompiler) writeTypeShadowedAssignment(exp *ast.AssignStmt, origName, renamedVar string) error {
|
|
569
|
+
if len(exp.Lhs) != 1 || len(exp.Rhs) != 1 {
|
|
570
|
+
return fmt.Errorf("type shadowing assignment must have exactly 1 LHS and 1 RHS")
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
lhsIdent, ok := exp.Lhs[0].(*ast.Ident)
|
|
574
|
+
if !ok {
|
|
575
|
+
return fmt.Errorf("type shadowing assignment LHS must be an identifier")
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Check if this variable needs VarRef
|
|
579
|
+
obj := c.objectOfIdent(lhsIdent)
|
|
580
|
+
needsVarRef := obj != nil && c.analysis.NeedsVarRef(obj)
|
|
581
|
+
|
|
582
|
+
// Store the mapping so that subsequent references to this variable use the renamed version
|
|
583
|
+
if obj != nil {
|
|
584
|
+
c.renamedVars[obj] = renamedVar
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if needsVarRef {
|
|
588
|
+
// For VarRef'd variables:
|
|
589
|
+
// let field_ = $.varRef($.markAsStructValue(new field({...})))
|
|
590
|
+
c.tsw.WriteLiterally("let ")
|
|
591
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
|
|
592
|
+
c.tsw.WriteLiterally(" = $.varRef(")
|
|
593
|
+
if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
|
|
594
|
+
return err
|
|
595
|
+
}
|
|
596
|
+
c.tsw.WriteLiterally(")")
|
|
597
|
+
} else {
|
|
598
|
+
// For non-VarRef variables:
|
|
599
|
+
// let field_ = $.markAsStructValue(new field({...}))
|
|
600
|
+
c.tsw.WriteLiterally("let ")
|
|
601
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedVar))
|
|
602
|
+
c.tsw.WriteLiterally(" = ")
|
|
603
|
+
if err := c.WriteValueExpr(exp.Rhs[0]); err != nil {
|
|
604
|
+
return err
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return nil
|
|
609
|
+
}
|
package/compiler/stmt-range.go
CHANGED
|
@@ -171,7 +171,7 @@ func (c *GoToTSCompiler) writeStringRange(exp *ast.RangeStmt) error {
|
|
|
171
171
|
|
|
172
172
|
if exp.Value != nil {
|
|
173
173
|
if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
|
|
174
|
-
c.tsw.WriteLiterally("
|
|
174
|
+
c.tsw.WriteLiterally("let ")
|
|
175
175
|
c.WriteIdent(ident, false)
|
|
176
176
|
c.tsw.WriteLiterally(" = _runes[")
|
|
177
177
|
c.tsw.WriteLiterally(indexVarName)
|
|
@@ -236,7 +236,7 @@ func (c *GoToTSCompiler) writeArraySliceWithKeyValue(exp *ast.RangeStmt, indexVa
|
|
|
236
236
|
c.tsw.WriteLine("")
|
|
237
237
|
|
|
238
238
|
if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
|
|
239
|
-
c.tsw.WriteLiterally("
|
|
239
|
+
c.tsw.WriteLiterally("let ")
|
|
240
240
|
c.WriteIdent(ident, false)
|
|
241
241
|
c.tsw.WriteLiterally(" = ")
|
|
242
242
|
if err := c.writeArraySliceExpression(exp.X, isPointer); err != nil {
|
package/compiler/stmt.go
CHANGED
|
@@ -368,6 +368,15 @@ func (c *GoToTSCompiler) WriteStmtExpr(exp *ast.ExprStmt) error {
|
|
|
368
368
|
return nil
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
// Defensive semicolon: if the expression will start with '(' in TypeScript,
|
|
372
|
+
// prepend a semicolon to prevent JavaScript from treating the previous line
|
|
373
|
+
// as a function call. This happens when:
|
|
374
|
+
// 1. CallExpr where Fun itself will be parenthesized (e.g., (await fn())())
|
|
375
|
+
// 2. Array/slice literals starting with '['
|
|
376
|
+
if c.needsDefensiveSemicolon(exp.X) {
|
|
377
|
+
c.tsw.WriteLiterally(";")
|
|
378
|
+
}
|
|
379
|
+
|
|
371
380
|
// Handle other expression statements
|
|
372
381
|
if err := c.WriteValueExpr(exp.X); err != nil { // Expression statement evaluates a value
|
|
373
382
|
return err
|
|
@@ -574,6 +583,12 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
|
|
|
574
583
|
}
|
|
575
584
|
}
|
|
576
585
|
}
|
|
586
|
+
|
|
587
|
+
// Special handling for primitive types that implement error interface
|
|
588
|
+
if c.writePrimitiveErrorWrapperIfNeeded(exp, res, i) {
|
|
589
|
+
continue
|
|
590
|
+
}
|
|
591
|
+
|
|
577
592
|
if err := c.WriteValueExpr(res); err != nil { // Return results are values
|
|
578
593
|
return err
|
|
579
594
|
}
|
|
@@ -586,6 +601,84 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
|
|
|
586
601
|
return nil
|
|
587
602
|
}
|
|
588
603
|
|
|
604
|
+
// writePrimitiveErrorWrapperIfNeeded checks if a return value is a primitive type
|
|
605
|
+
// that implements the error interface, and if so, wraps it with $.wrapPrimitiveError.
|
|
606
|
+
// Returns true if the wrapper was written, false otherwise.
|
|
607
|
+
func (c *GoToTSCompiler) writePrimitiveErrorWrapperIfNeeded(retStmt *ast.ReturnStmt, res ast.Expr, resultIndex int) bool {
|
|
608
|
+
// Get the expected return type for this position
|
|
609
|
+
nodeInfo := c.analysis.NodeData[retStmt]
|
|
610
|
+
if nodeInfo == nil || nodeInfo.EnclosingFuncDecl == nil {
|
|
611
|
+
return false
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
funcDecl := nodeInfo.EnclosingFuncDecl
|
|
615
|
+
if funcDecl.Type.Results == nil {
|
|
616
|
+
return false
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Find the expected return type for this result index
|
|
620
|
+
var expectedType types.Type
|
|
621
|
+
resultIdx := 0
|
|
622
|
+
for _, field := range funcDecl.Type.Results.List {
|
|
623
|
+
count := len(field.Names)
|
|
624
|
+
if count == 0 {
|
|
625
|
+
count = 1
|
|
626
|
+
}
|
|
627
|
+
for j := 0; j < count; j++ {
|
|
628
|
+
if resultIdx == resultIndex {
|
|
629
|
+
expectedType = c.pkg.TypesInfo.TypeOf(field.Type)
|
|
630
|
+
break
|
|
631
|
+
}
|
|
632
|
+
resultIdx++
|
|
633
|
+
}
|
|
634
|
+
if expectedType != nil {
|
|
635
|
+
break
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if expectedType == nil {
|
|
640
|
+
return false
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Check if the expected type is the error interface
|
|
644
|
+
if iface, ok := expectedType.Underlying().(*types.Interface); !ok || iface.String() != "interface{Error() string}" {
|
|
645
|
+
return false
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Get the actual type of the return expression
|
|
649
|
+
actualType := c.pkg.TypesInfo.TypeOf(res)
|
|
650
|
+
if actualType == nil {
|
|
651
|
+
return false
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Check if the actual type is a wrapper type (named type with basic underlying type)
|
|
655
|
+
if !c.isWrapperType(actualType) {
|
|
656
|
+
return false
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Check if the actual type has an Error() method
|
|
660
|
+
if !c.typeHasMethods(actualType, "Error") {
|
|
661
|
+
return false
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Get the qualified type name for the Error function
|
|
665
|
+
typeName := c.getQualifiedTypeName(actualType)
|
|
666
|
+
if typeName == "" {
|
|
667
|
+
return false
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Write: $.wrapPrimitiveError(value, TypeName_Error)
|
|
671
|
+
c.tsw.WriteLiterally("$.wrapPrimitiveError(")
|
|
672
|
+
if err := c.WriteValueExpr(res); err != nil {
|
|
673
|
+
return false
|
|
674
|
+
}
|
|
675
|
+
c.tsw.WriteLiterally(", ")
|
|
676
|
+
c.tsw.WriteLiterally(typeName)
|
|
677
|
+
c.tsw.WriteLiterally("_Error)")
|
|
678
|
+
|
|
679
|
+
return true
|
|
680
|
+
}
|
|
681
|
+
|
|
589
682
|
// WriteStmtBlock translates a Go block statement (`ast.BlockStmt`), typically
|
|
590
683
|
// `{ ...stmts... }`, into its TypeScript equivalent, carefully preserving
|
|
591
684
|
// comments and blank lines to maintain code readability and structure.
|
|
@@ -1129,3 +1222,38 @@ func (c *GoToTSCompiler) substituteExprForShadowing(expr ast.Expr, shadowingInfo
|
|
|
1129
1222
|
func (c *GoToTSCompiler) isBuiltinFunction(name string) bool {
|
|
1130
1223
|
return builtinFunctions[name]
|
|
1131
1224
|
}
|
|
1225
|
+
|
|
1226
|
+
// needsDefensiveSemicolon determines if an expression will generate TypeScript
|
|
1227
|
+
// code starting with '(' or '[', which would require a defensive semicolon to
|
|
1228
|
+
// prevent JavaScript from treating the previous line as a function call.
|
|
1229
|
+
func (c *GoToTSCompiler) needsDefensiveSemicolon(expr ast.Expr) bool {
|
|
1230
|
+
switch e := expr.(type) {
|
|
1231
|
+
case *ast.CallExpr:
|
|
1232
|
+
// Check if the function being called will be parenthesized
|
|
1233
|
+
// This happens when Fun is itself a CallExpr, TypeAssertExpr, or other complex expression
|
|
1234
|
+
switch e.Fun.(type) {
|
|
1235
|
+
case *ast.CallExpr:
|
|
1236
|
+
// (fn())() - needs defensive semicolon
|
|
1237
|
+
return true
|
|
1238
|
+
case *ast.TypeAssertExpr:
|
|
1239
|
+
// (x.(T))() - needs defensive semicolon
|
|
1240
|
+
return true
|
|
1241
|
+
case *ast.IndexExpr:
|
|
1242
|
+
// Could generate (arr[i])() if indexed result is called
|
|
1243
|
+
// But typically doesn't need defensive semicolon as arr[i]() is fine
|
|
1244
|
+
return false
|
|
1245
|
+
case *ast.ParenExpr:
|
|
1246
|
+
// Already parenthesized - needs defensive semicolon
|
|
1247
|
+
return true
|
|
1248
|
+
}
|
|
1249
|
+
case *ast.CompositeLit:
|
|
1250
|
+
// Array/slice literals start with '['
|
|
1251
|
+
if _, isArray := e.Type.(*ast.ArrayType); isArray {
|
|
1252
|
+
return true
|
|
1253
|
+
}
|
|
1254
|
+
case *ast.ParenExpr:
|
|
1255
|
+
// Parenthesized expressions start with '('
|
|
1256
|
+
return true
|
|
1257
|
+
}
|
|
1258
|
+
return false
|
|
1259
|
+
}
|
package/compiler/type-utils.go
CHANGED
|
@@ -25,11 +25,50 @@ func (c *GoToTSCompiler) isRuneSliceType(t types.Type) bool {
|
|
|
25
25
|
return false
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// isStringType checks if a type is string
|
|
28
|
+
// isStringType checks if a type is string or could be string (e.g., type parameter)
|
|
29
29
|
func (c *GoToTSCompiler) isStringType(t types.Type) bool {
|
|
30
30
|
if basic, isBasic := t.Underlying().(*types.Basic); isBasic {
|
|
31
31
|
return basic.Kind() == types.String || basic.Kind() == types.UntypedString
|
|
32
32
|
}
|
|
33
|
+
// Handle type parameters (e.g., Bytes ~[]byte | ~string)
|
|
34
|
+
if typeParam, isTypeParam := t.(*types.TypeParam); isTypeParam {
|
|
35
|
+
constraint := typeParam.Constraint()
|
|
36
|
+
if constraintIface, ok := constraint.(*types.Interface); ok {
|
|
37
|
+
return c.constraintIncludesString(constraintIface)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// constraintIncludesString checks if a type constraint includes string
|
|
44
|
+
func (c *GoToTSCompiler) constraintIncludesString(constraint *types.Interface) bool {
|
|
45
|
+
// Check if the constraint has type terms that include string
|
|
46
|
+
if constraint.IsMethodSet() {
|
|
47
|
+
return false // Pure method interface, no type terms
|
|
48
|
+
}
|
|
49
|
+
// For union constraints like []byte | string, check each term
|
|
50
|
+
for i := 0; i < constraint.NumEmbeddeds(); i++ {
|
|
51
|
+
embedded := constraint.EmbeddedType(i)
|
|
52
|
+
// Check if embedded is a union
|
|
53
|
+
if union, isUnion := embedded.(*types.Union); isUnion {
|
|
54
|
+
for j := 0; j < union.Len(); j++ {
|
|
55
|
+
term := union.Term(j)
|
|
56
|
+
termType := term.Type()
|
|
57
|
+
// Check if term is string or ~string
|
|
58
|
+
if basic, isBasic := termType.Underlying().(*types.Basic); isBasic {
|
|
59
|
+
if basic.Kind() == types.String {
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Check direct embedded type
|
|
66
|
+
if basic, isBasic := embedded.Underlying().(*types.Basic); isBasic {
|
|
67
|
+
if basic.Kind() == types.String {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
33
72
|
return false
|
|
34
73
|
}
|
|
35
74
|
|