goscript 0.0.60 → 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 +974 -369
- package/compiler/assignment.go +72 -0
- package/compiler/compiler.go +74 -15
- package/compiler/composite-lit.go +29 -8
- package/compiler/decl.go +67 -98
- package/compiler/expr-call-async.go +26 -1
- package/compiler/expr-call-builtins.go +60 -4
- package/compiler/expr-call-helpers.go +182 -0
- package/compiler/expr-call-type-conversion.go +37 -5
- package/compiler/expr-call.go +25 -33
- package/compiler/expr-selector.go +71 -1
- package/compiler/expr-type.go +49 -3
- package/compiler/expr.go +37 -28
- package/compiler/index.test.ts +3 -1
- package/compiler/lit.go +13 -4
- package/compiler/spec-struct.go +42 -9
- package/compiler/spec-value.go +2 -2
- package/compiler/spec.go +42 -5
- package/compiler/stmt-assign.go +71 -0
- package/compiler/stmt-range.go +2 -2
- package/compiler/stmt.go +130 -10
- package/compiler/type-utils.go +40 -16
- 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 +51 -21
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +28 -2
- package/dist/gs/builtin/type.js +132 -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/internal/byteorder/index.d.ts +6 -0
- package/dist/gs/internal/byteorder/index.js +34 -0
- package/dist/gs/internal/byteorder/index.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +3 -3
- package/dist/gs/reflect/index.js +2 -2
- 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 +55 -8
- package/dist/gs/reflect/type.js +889 -23
- 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 +32 -0
- package/dist/gs/slices/slices.js +81 -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 -16
- package/gs/builtin/builtin.ts +27 -2
- package/gs/builtin/errors.ts +12 -0
- package/gs/builtin/slice.ts +77 -14
- package/gs/builtin/type.ts +167 -2
- package/gs/bytes/reader.gs.ts +2 -2
- package/gs/internal/byteorder/index.ts +40 -0
- package/gs/math/hypot.gs.test.ts +3 -1
- package/gs/math/pow10.gs.test.ts +5 -4
- package/gs/reflect/index.ts +6 -3
- package/gs/reflect/map.test.ts +7 -6
- package/gs/reflect/map.ts +49 -7
- package/gs/reflect/type.ts +1139 -43
- package/gs/reflect/types.ts +34 -21
- package/gs/reflect/value.ts +12 -6
- package/gs/slices/slices.ts +92 -0
- package/gs/sync/atomic/type.gs.ts +14 -5
- package/gs/sync/meta.json +1 -1
- package/gs/unicode/utf8/utf8.ts +12 -8
- package/package.json +13 -13
package/compiler/expr.go
CHANGED
|
@@ -251,34 +251,6 @@ func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
|
|
|
251
251
|
return "unknown"
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
// getFinalUnderlyingType traverses the chain of named types to find the ultimate underlying type.
|
|
255
|
-
// This handles cases like: type A B; type B C; type C string.
|
|
256
|
-
// Returns the final underlying type and whether the original type was a named type.
|
|
257
|
-
func (c *GoToTSCompiler) getFinalUnderlyingType(t types.Type) (types.Type, bool) {
|
|
258
|
-
if t == nil {
|
|
259
|
-
return nil, false
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Check if this is a named type
|
|
263
|
-
namedType, isNamed := t.(*types.Named)
|
|
264
|
-
if !isNamed {
|
|
265
|
-
return t, false
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Follow the chain of named types to find the ultimate underlying type
|
|
269
|
-
ultimate := namedType
|
|
270
|
-
for {
|
|
271
|
-
underlying := ultimate.Underlying()
|
|
272
|
-
if underlyingNamed, isNamedUnderlying := underlying.(*types.Named); isNamedUnderlying {
|
|
273
|
-
// Continue following the chain
|
|
274
|
-
ultimate = underlyingNamed
|
|
275
|
-
} else {
|
|
276
|
-
// We've reached the final underlying type
|
|
277
|
-
return underlying, true
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
254
|
// WriteBinaryExpr translates a Go binary expression (`ast.BinaryExpr`) into its
|
|
283
255
|
// TypeScript equivalent.
|
|
284
256
|
// It handles several cases:
|
|
@@ -469,9 +441,46 @@ func (c *GoToTSCompiler) WriteBinaryExpr(exp *ast.BinaryExpr) error {
|
|
|
469
441
|
c.tsw.WriteLiterally("(") // Add opening parenthesis for bitwise operations
|
|
470
442
|
}
|
|
471
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
|
+
}
|
|
472
478
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
473
479
|
return fmt.Errorf("failed to write binary expression left operand: %w", err)
|
|
474
480
|
}
|
|
481
|
+
if needsNumberCast {
|
|
482
|
+
c.tsw.WriteLiterally(")")
|
|
483
|
+
}
|
|
475
484
|
|
|
476
485
|
c.tsw.WriteLiterally(" ")
|
|
477
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)
|
|
@@ -429,8 +440,19 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
429
440
|
// Add code to register the type with the runtime type system
|
|
430
441
|
c.tsw.WriteLine("")
|
|
431
442
|
c.tsw.WriteLinef("// Register this type with the runtime type system")
|
|
443
|
+
|
|
444
|
+
// Build full package path name for registration
|
|
445
|
+
structName := className
|
|
446
|
+
pkgPath := c.pkg.Types.Path()
|
|
447
|
+
pkgName := c.pkg.Types.Name()
|
|
448
|
+
if pkgPath != "" && pkgName != "main" {
|
|
449
|
+
structName = pkgPath + "." + className
|
|
450
|
+
} else if pkgName == "main" {
|
|
451
|
+
structName = "main." + className
|
|
452
|
+
}
|
|
453
|
+
|
|
432
454
|
c.tsw.WriteLinef("static __typeInfo = $.registerStructType(")
|
|
433
|
-
c.tsw.WriteLinef(" '%s',",
|
|
455
|
+
c.tsw.WriteLinef(" '%s',", structName)
|
|
434
456
|
c.tsw.WriteLinef(" new %s(),", className)
|
|
435
457
|
c.tsw.WriteLiterally(" [")
|
|
436
458
|
// Collect methods for the struct type
|
|
@@ -468,13 +490,23 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
|
|
|
468
490
|
if fieldKeyName == "_" {
|
|
469
491
|
continue
|
|
470
492
|
}
|
|
471
|
-
// fieldTsType := c.getTypeString(field.Type())
|
|
472
493
|
if !firstField {
|
|
473
494
|
c.tsw.WriteLiterally(", ")
|
|
474
495
|
}
|
|
475
496
|
firstField = false
|
|
476
497
|
c.tsw.WriteLiterallyf("%q: ", fieldKeyName)
|
|
477
|
-
|
|
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
|
+
}
|
|
478
510
|
}
|
|
479
511
|
c.tsw.WriteLiterally("}")
|
|
480
512
|
c.tsw.WriteLine("")
|
|
@@ -532,15 +564,16 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
|
|
|
532
564
|
fieldType = ptr.Elem()
|
|
533
565
|
}
|
|
534
566
|
|
|
535
|
-
if
|
|
536
|
-
embeddedName := named.Obj().Name()
|
|
567
|
+
if _, ok := fieldType.(*types.Named); ok {
|
|
537
568
|
// Check if the embedded type is an interface
|
|
538
569
|
if _, isInterface := fieldType.Underlying().(*types.Interface); isInterface {
|
|
539
570
|
// For embedded interfaces, use the full qualified interface type
|
|
540
571
|
embeddedTypeMap[c.getEmbeddedFieldKeyName(field.Type())] = c.getTypeString(field.Type())
|
|
541
572
|
} else {
|
|
542
573
|
// For embedded structs, use ConstructorParameters for field-based initialization
|
|
543
|
-
|
|
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)
|
|
544
577
|
}
|
|
545
578
|
}
|
|
546
579
|
continue
|
|
@@ -562,7 +595,7 @@ func (c *GoToTSCompiler) generateFlattenedInitTypeString(structType *types.Named
|
|
|
562
595
|
for name := range fieldMap {
|
|
563
596
|
fieldNames = append(fieldNames, name)
|
|
564
597
|
}
|
|
565
|
-
|
|
598
|
+
slices.Sort(fieldNames)
|
|
566
599
|
|
|
567
600
|
var fieldDefs []string
|
|
568
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)
|
|
@@ -121,7 +122,7 @@ func (c *GoToTSCompiler) writeEmbeddedFieldInitializer(fieldName string, fieldTy
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
// For structs, instantiate with provided fields
|
|
124
|
-
typeForNew :=
|
|
125
|
+
typeForNew := c.getTypeString(fieldType)
|
|
125
126
|
c.tsw.WriteLiterallyf("new %s(init?.%s)", typeForNew, fieldName)
|
|
126
127
|
}
|
|
127
128
|
|
|
@@ -224,6 +225,12 @@ func (c *GoToTSCompiler) writeNamedTypeZeroValue(named *types.Named) {
|
|
|
224
225
|
return
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
// Check if underlying type is a function
|
|
229
|
+
if _, isFunc := named.Underlying().(*types.Signature); isFunc {
|
|
230
|
+
c.tsw.WriteLiterally("null")
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
227
234
|
// For non-struct, non-interface named types, use constructor
|
|
228
235
|
c.tsw.WriteLiterally("new ")
|
|
229
236
|
c.WriteNamedType(named)
|
|
@@ -254,6 +261,12 @@ func (c *GoToTSCompiler) writeTypeAliasZeroValue(alias *types.Alias, astType ast
|
|
|
254
261
|
return
|
|
255
262
|
}
|
|
256
263
|
|
|
264
|
+
// Check if underlying type is a function
|
|
265
|
+
if _, isFunc := alias.Underlying().(*types.Signature); isFunc {
|
|
266
|
+
c.tsw.WriteLiterally("null")
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
257
270
|
// For non-struct, non-interface type aliases, use constructor
|
|
258
271
|
c.tsw.WriteLiterally("new ")
|
|
259
272
|
// Use AST type information if available to preserve qualified names
|
|
@@ -394,11 +407,20 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
|
|
|
394
407
|
c.WriteTypeExpr(funcDecl.Type.Results.List[0].Type)
|
|
395
408
|
} else {
|
|
396
409
|
c.tsw.WriteLiterally("[")
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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)
|
|
400
423
|
}
|
|
401
|
-
c.WriteTypeExpr(field.Type)
|
|
402
424
|
}
|
|
403
425
|
c.tsw.WriteLiterally("]")
|
|
404
426
|
}
|
|
@@ -521,7 +543,15 @@ func (c *GoToTSCompiler) WriteInterfaceTypeSpec(a *ast.TypeSpec, t *ast.Interfac
|
|
|
521
543
|
c.tsw.WriteLine("")
|
|
522
544
|
|
|
523
545
|
// Add code to register the interface with the runtime system
|
|
546
|
+
// Build full package path name for registration
|
|
524
547
|
interfaceName := a.Name.Name
|
|
548
|
+
pkgPath := c.pkg.Types.Path()
|
|
549
|
+
pkgName := c.pkg.Types.Name()
|
|
550
|
+
if pkgPath != "" && pkgName != "main" {
|
|
551
|
+
interfaceName = pkgPath + "." + interfaceName
|
|
552
|
+
} else if pkgName == "main" {
|
|
553
|
+
interfaceName = "main." + interfaceName
|
|
554
|
+
}
|
|
525
555
|
c.tsw.WriteLine("")
|
|
526
556
|
c.tsw.WriteLinef("$.registerInterfaceType(")
|
|
527
557
|
c.tsw.WriteLinef(" '%s',", interfaceName)
|
|
@@ -605,6 +635,13 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
|
605
635
|
importVars: make(map[string]struct{}),
|
|
606
636
|
}
|
|
607
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
|
+
|
|
608
645
|
c.tsw.WriteImport(impName, tsImportPath+"/index.js")
|
|
609
646
|
}
|
|
610
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
|
|
@@ -528,16 +537,8 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
|
|
|
528
537
|
nodeInfo := c.analysis.NodeData[exp]
|
|
529
538
|
if nodeInfo != nil && nodeInfo.IsBareReturn {
|
|
530
539
|
var namedReturns []string
|
|
531
|
-
if nodeInfo.
|
|
532
|
-
|
|
533
|
-
if funcInfo := c.analysis.FunctionData[obj]; funcInfo != nil {
|
|
534
|
-
namedReturns = funcInfo.NamedReturns
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
} else if nodeInfo.EnclosingFuncLit != nil {
|
|
538
|
-
if funcInfo := c.analysis.FuncLitData[nodeInfo.EnclosingFuncLit]; funcInfo != nil {
|
|
539
|
-
namedReturns = funcInfo.NamedReturns
|
|
540
|
-
}
|
|
540
|
+
if funcInfo := c.analysis.GetFunctionInfoFromContext(nodeInfo, c.pkg); funcInfo != nil {
|
|
541
|
+
namedReturns = funcInfo.NamedReturns
|
|
541
542
|
}
|
|
542
543
|
|
|
543
544
|
if len(namedReturns) == 1 {
|
|
@@ -582,6 +583,12 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
|
|
|
582
583
|
}
|
|
583
584
|
}
|
|
584
585
|
}
|
|
586
|
+
|
|
587
|
+
// Special handling for primitive types that implement error interface
|
|
588
|
+
if c.writePrimitiveErrorWrapperIfNeeded(exp, res, i) {
|
|
589
|
+
continue
|
|
590
|
+
}
|
|
591
|
+
|
|
585
592
|
if err := c.WriteValueExpr(res); err != nil { // Return results are values
|
|
586
593
|
return err
|
|
587
594
|
}
|
|
@@ -594,6 +601,84 @@ func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
|
|
|
594
601
|
return nil
|
|
595
602
|
}
|
|
596
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
|
+
|
|
597
682
|
// WriteStmtBlock translates a Go block statement (`ast.BlockStmt`), typically
|
|
598
683
|
// `{ ...stmts... }`, into its TypeScript equivalent, carefully preserving
|
|
599
684
|
// comments and blank lines to maintain code readability and structure.
|
|
@@ -1137,3 +1222,38 @@ func (c *GoToTSCompiler) substituteExprForShadowing(expr ast.Expr, shadowingInfo
|
|
|
1137
1222
|
func (c *GoToTSCompiler) isBuiltinFunction(name string) bool {
|
|
1138
1223
|
return builtinFunctions[name]
|
|
1139
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
|
+
}
|