goscript 0.0.58 → 0.0.60
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 +40 -33
- package/compiler/analysis.go +184 -43
- package/compiler/assignment.go +163 -217
- package/compiler/compiler.go +35 -31
- package/compiler/composite-lit.go +233 -196
- package/compiler/constraint.go +88 -0
- package/compiler/decl.go +82 -24
- package/compiler/expr-call-async.go +20 -34
- package/compiler/expr-call-builtins.go +19 -0
- package/compiler/expr-call-helpers.go +0 -28
- package/compiler/expr-call-make.go +93 -343
- package/compiler/expr-call-type-conversion.go +221 -249
- package/compiler/expr-call.go +70 -69
- package/compiler/expr-selector.go +21 -24
- package/compiler/expr.go +3 -60
- package/compiler/protobuf.go +180 -36
- package/compiler/spec-value.go +132 -24
- package/compiler/spec.go +14 -55
- package/compiler/stmt-assign.go +338 -356
- package/compiler/stmt-range.go +4 -24
- package/compiler/stmt.go +92 -203
- package/compiler/type-utils.go +185 -0
- package/compiler/type.go +26 -80
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js +3 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +8 -2
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/fmt/fmt.js +113 -16
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/runtime/runtime.d.ts +1 -1
- package/dist/gs/runtime/runtime.js +1 -1
- package/dist/gs/slices/slices.d.ts +23 -0
- package/dist/gs/slices/slices.js +61 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/go.mod +10 -10
- package/go.sum +22 -14
- package/gs/builtin/slice.ts +5 -2
- package/gs/builtin/type.ts +13 -6
- package/gs/fmt/fmt.test.ts +176 -0
- package/gs/fmt/fmt.ts +109 -18
- package/gs/runtime/runtime.ts +1 -1
- package/gs/slices/slices.ts +68 -0
- package/package.json +3 -3
package/compiler/expr-call.go
CHANGED
|
@@ -169,7 +169,7 @@ func (c *GoToTSCompiler) writeCallArguments(exp *ast.CallExpr) error {
|
|
|
169
169
|
func (c *GoToTSCompiler) writeArgumentWithTypeHandling(arg ast.Expr, funcSig *types.Signature, argIndex int) error {
|
|
170
170
|
if funcSig != nil && argIndex < funcSig.Params().Len() {
|
|
171
171
|
paramType := funcSig.Params().At(argIndex).Type()
|
|
172
|
-
isWrapper := c.
|
|
172
|
+
isWrapper := c.isWrapperType(paramType)
|
|
173
173
|
|
|
174
174
|
if isWrapper {
|
|
175
175
|
// For wrapper types (now type aliases), no auto-wrapping is needed
|
|
@@ -202,7 +202,52 @@ func (c *GoToTSCompiler) writeArgumentWithTypeHandling(arg ast.Expr, funcSig *ty
|
|
|
202
202
|
return nil
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// resolveImportAlias returns the import alias for a given package
|
|
206
|
+
// This is the single source of truth for import alias resolution
|
|
207
|
+
func (c *GoToTSCompiler) resolveImportAlias(pkg *types.Package) (alias string, found bool) {
|
|
208
|
+
if c.analysis == nil || pkg == nil {
|
|
209
|
+
return "", false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
pkgName := pkg.Name()
|
|
213
|
+
|
|
214
|
+
// Try to match by the actual package name
|
|
215
|
+
if _, exists := c.analysis.Imports[pkgName]; exists {
|
|
216
|
+
return pkgName, true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return "", false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// getQualifiedTypeName returns the qualified type name for a Named or Alias type
|
|
223
|
+
// Returns empty string if the type is not Named or Alias, or has no valid object
|
|
224
|
+
func (c *GoToTSCompiler) getQualifiedTypeName(t types.Type) string {
|
|
225
|
+
var obj *types.TypeName
|
|
226
|
+
|
|
227
|
+
if namedType, ok := t.(*types.Named); ok {
|
|
228
|
+
obj = namedType.Obj()
|
|
229
|
+
} else if aliasType, ok := t.(*types.Alias); ok {
|
|
230
|
+
obj = aliasType.Obj()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if obj == nil || obj.Pkg() == nil {
|
|
234
|
+
return ""
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if obj.Pkg() != c.pkg.Types {
|
|
238
|
+
// Imported type
|
|
239
|
+
if alias, found := c.resolveImportAlias(obj.Pkg()); found {
|
|
240
|
+
return alias + "." + obj.Name()
|
|
241
|
+
}
|
|
242
|
+
return obj.Name()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Local type
|
|
246
|
+
return obj.Name()
|
|
247
|
+
}
|
|
248
|
+
|
|
205
249
|
// getImportAlias returns the import alias for a given package path
|
|
250
|
+
// Deprecated: use resolveImportAlias instead for better type safety
|
|
206
251
|
func (c *GoToTSCompiler) getImportAlias(pkgPath string) string {
|
|
207
252
|
if c.analysis == nil {
|
|
208
253
|
return ""
|
|
@@ -234,7 +279,7 @@ func (c *GoToTSCompiler) getImportAlias(pkgPath string) string {
|
|
|
234
279
|
func (c *GoToTSCompiler) writeAutoWrappedArgument(arg ast.Expr, expectedType types.Type) error {
|
|
235
280
|
// For wrapper types (now type aliases), no auto-wrapping is needed
|
|
236
281
|
// Just use type casting if the types don't match exactly
|
|
237
|
-
if c.
|
|
282
|
+
if c.isWrapperType(expectedType) {
|
|
238
283
|
argType := c.pkg.TypesInfo.TypeOf(arg)
|
|
239
284
|
|
|
240
285
|
// Only add type casting if needed
|
|
@@ -272,73 +317,12 @@ func (c *GoToTSCompiler) writeWrapperTypeMethodCall(exp *ast.CallExpr, selectorE
|
|
|
272
317
|
}
|
|
273
318
|
|
|
274
319
|
// Check if this is a wrapper type using the analysis
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// Special handling for type aliases to basic types that might have wrapper functions
|
|
278
|
-
// Even if IsNamedBasicType returns false, we should check for known wrapper type patterns
|
|
279
|
-
var typeName string
|
|
280
|
-
if !isWrapperType {
|
|
281
|
-
// Check if this is a type alias to a basic type that might have wrapper methods
|
|
282
|
-
if aliasType, ok := baseType.(*types.Alias); ok {
|
|
283
|
-
if aliasType.Obj() != nil && aliasType.Obj().Pkg() != nil {
|
|
284
|
-
// Check if the underlying type is a basic type
|
|
285
|
-
underlying := aliasType.Underlying()
|
|
286
|
-
if _, isBasic := underlying.(*types.Basic); isBasic {
|
|
287
|
-
// This is a type alias to a basic type - treat it as a potential wrapper type
|
|
288
|
-
isWrapperType = true
|
|
289
|
-
if aliasType.Obj().Pkg() != c.pkg.Types {
|
|
290
|
-
// Imported type alias like os.FileMode
|
|
291
|
-
if importAlias := c.getImportAlias(aliasType.Obj().Pkg().Path()); importAlias != "" {
|
|
292
|
-
typeName = importAlias + "." + aliasType.Obj().Name()
|
|
293
|
-
} else {
|
|
294
|
-
typeName = aliasType.Obj().Name()
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
// Local type alias
|
|
298
|
-
typeName = aliasType.Obj().Name()
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if !isWrapperType {
|
|
320
|
+
if !c.isWrapperType(baseType) {
|
|
306
321
|
return false, nil
|
|
307
322
|
}
|
|
308
323
|
|
|
309
|
-
// Get the type name for the function call
|
|
310
|
-
|
|
311
|
-
if namedType, ok := baseType.(*types.Named); ok {
|
|
312
|
-
if obj := namedType.Obj(); obj != nil {
|
|
313
|
-
if obj.Pkg() != nil && obj.Pkg() != c.pkg.Types {
|
|
314
|
-
// Imported type like os.FileMode
|
|
315
|
-
if importAlias := c.getImportAlias(obj.Pkg().Path()); importAlias != "" {
|
|
316
|
-
typeName = importAlias + "." + obj.Name()
|
|
317
|
-
} else {
|
|
318
|
-
typeName = obj.Name()
|
|
319
|
-
}
|
|
320
|
-
} else {
|
|
321
|
-
// Local type
|
|
322
|
-
typeName = obj.Name()
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
} else if aliasType, ok := baseType.(*types.Alias); ok {
|
|
326
|
-
if obj := aliasType.Obj(); obj != nil {
|
|
327
|
-
if obj.Pkg() != nil && obj.Pkg() != c.pkg.Types {
|
|
328
|
-
// Imported type alias
|
|
329
|
-
if importAlias := c.getImportAlias(obj.Pkg().Path()); importAlias != "" {
|
|
330
|
-
typeName = importAlias + "." + obj.Name()
|
|
331
|
-
} else {
|
|
332
|
-
typeName = obj.Name()
|
|
333
|
-
}
|
|
334
|
-
} else {
|
|
335
|
-
// Local type alias
|
|
336
|
-
typeName = obj.Name()
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
324
|
+
// Get the qualified type name for the function call
|
|
325
|
+
typeName := c.getQualifiedTypeName(baseType)
|
|
342
326
|
if typeName == "" {
|
|
343
327
|
return false, nil
|
|
344
328
|
}
|
|
@@ -369,9 +353,26 @@ func (c *GoToTSCompiler) writeWrapperTypeMethodCall(exp *ast.CallExpr, selectorE
|
|
|
369
353
|
}
|
|
370
354
|
|
|
371
355
|
if receiverNeedsVarRef {
|
|
372
|
-
// For pointer receivers, we need to pass the VarRef
|
|
373
|
-
//
|
|
374
|
-
if
|
|
356
|
+
// For pointer receivers, we need to pass the VarRef (not the value)
|
|
357
|
+
// Check if the receiver expression is an identifier that's already a VarRef
|
|
358
|
+
if ident, ok := selectorExpr.X.(*ast.Ident); ok {
|
|
359
|
+
if obj := c.pkg.TypesInfo.ObjectOf(ident); obj != nil {
|
|
360
|
+
if c.analysis != nil && c.analysis.NeedsVarRef(obj) {
|
|
361
|
+
// This variable is already a VarRef, pass it directly
|
|
362
|
+
c.tsw.WriteLiterally(ident.Name)
|
|
363
|
+
} else {
|
|
364
|
+
// Not a VarRef, write the value expression
|
|
365
|
+
if err := c.WriteValueExpr(selectorExpr.X); err != nil {
|
|
366
|
+
return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
if err := c.WriteValueExpr(selectorExpr.X); err != nil {
|
|
371
|
+
return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
} else if selExpr, ok := selectorExpr.X.(*ast.SelectorExpr); ok {
|
|
375
|
+
// Convert p.field to p._fields.field for struct field access
|
|
375
376
|
if baseIdent, ok := selExpr.X.(*ast.Ident); ok {
|
|
376
377
|
c.tsw.WriteLiterally(baseIdent.Name)
|
|
377
378
|
c.tsw.WriteLiterally("._fields.")
|
|
@@ -130,30 +130,9 @@ func (c *GoToTSCompiler) WriteSelectorExpr(exp *ast.SelectorExpr) error {
|
|
|
130
130
|
// In Go, accessing fields or calling methods on nil pointers/interfaces panics, so we should throw in TypeScript
|
|
131
131
|
baseType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
132
132
|
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
isMethodReceiverAlias :=
|
|
136
|
-
if baseIdent, ok := exp.X.(*ast.Ident); ok {
|
|
137
|
-
// Check if this identifier refers to a method receiver
|
|
138
|
-
// Method receivers are typically named variables that alias `this`
|
|
139
|
-
if obj := c.pkg.TypesInfo.ObjectOf(baseIdent); obj != nil {
|
|
140
|
-
if varObj, ok := obj.(*types.Var); ok {
|
|
141
|
-
// Check if this is a local variable in a method context
|
|
142
|
-
// Method receiver aliases are local variables with pointer types that represent `this`
|
|
143
|
-
if _, isPtr := varObj.Type().(*types.Pointer); isPtr {
|
|
144
|
-
// Additional check: only consider it a method receiver alias if the variable name
|
|
145
|
-
// suggests it's an alias (single letter names are common for aliases like `b`, `r`, etc.)
|
|
146
|
-
// and it's not a common variable name like `f` which is often used for regular variables
|
|
147
|
-
varName := baseIdent.Name
|
|
148
|
-
if len(varName) == 1 && varName != "f" {
|
|
149
|
-
// This is likely a method receiver alias like `const b = this`
|
|
150
|
-
// We should treat it as a regular object access, not a pointer access
|
|
151
|
-
isMethodReceiverAlias = true
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
133
|
+
// Check if this is a method receiver alias (e.g., `c` in `const c = this`)
|
|
134
|
+
// Use proper AST/types analysis instead of heuristics
|
|
135
|
+
isMethodReceiverAlias := c.isReceiverAlias(exp.X)
|
|
157
136
|
|
|
158
137
|
if baseType != nil && !isMethodReceiverAlias {
|
|
159
138
|
// Check if the base is a pointer type
|
|
@@ -274,3 +253,21 @@ func (c *GoToTSCompiler) writeMethodValue(exp *ast.SelectorExpr, selection *type
|
|
|
274
253
|
|
|
275
254
|
return nil
|
|
276
255
|
}
|
|
256
|
+
|
|
257
|
+
// isReceiverAlias detects if a variable is likely a receiver alias (e.g., const c = this)
|
|
258
|
+
// These variables are guaranteed to be non-null and don't need null assertions
|
|
259
|
+
func (c *GoToTSCompiler) isReceiverAlias(expr ast.Expr) bool {
|
|
260
|
+
// Only check identifiers - receiver aliases are always identifiers
|
|
261
|
+
ident, ok := expr.(*ast.Ident)
|
|
262
|
+
if !ok {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// If analysis marked this identifier with a mapping, treat it as a receiver alias
|
|
267
|
+
if mapped := c.analysis.GetIdentifierMapping(ident); mapped != "" {
|
|
268
|
+
return true
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Fallback conservative behavior
|
|
272
|
+
return false
|
|
273
|
+
}
|
package/compiler/expr.go
CHANGED
|
@@ -19,7 +19,7 @@ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
|
19
19
|
// Check if the index is a type expression (identifier that refers to a type)
|
|
20
20
|
if indexIdent, isIdent := exp.Index.(*ast.Ident); isIdent {
|
|
21
21
|
// Check if this identifier refers to a type
|
|
22
|
-
if obj := c.
|
|
22
|
+
if obj := c.objectOfIdent(indexIdent); obj != nil {
|
|
23
23
|
if _, isTypeName := obj.(*types.TypeName); isTypeName {
|
|
24
24
|
// This is a generic function instantiation: f[T] -> f<T>
|
|
25
25
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
@@ -57,7 +57,7 @@ func (c *GoToTSCompiler) WriteIndexExpr(exp *ast.IndexExpr) error {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// Check if it's a string type
|
|
60
|
-
if
|
|
60
|
+
if c.isStringType(tv.Type) {
|
|
61
61
|
c.tsw.WriteLiterally("$.indexString(")
|
|
62
62
|
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
63
63
|
return err
|
|
@@ -279,21 +279,6 @@ func (c *GoToTSCompiler) getFinalUnderlyingType(t types.Type) (types.Type, bool)
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
// isNamedNumericType checks if a given type is a named type with an underlying numeric type.
|
|
283
|
-
func (c *GoToTSCompiler) isNamedNumericType(t types.Type) bool {
|
|
284
|
-
finalType, wasNamed := c.getFinalUnderlyingType(t)
|
|
285
|
-
if !wasNamed {
|
|
286
|
-
return false
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if basicType, isBasic := finalType.(*types.Basic); isBasic {
|
|
290
|
-
info := basicType.Info()
|
|
291
|
-
return (info&types.IsInteger) != 0 || (info&types.IsFloat) != 0
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return false
|
|
295
|
-
}
|
|
296
|
-
|
|
297
282
|
// WriteBinaryExpr translates a Go binary expression (`ast.BinaryExpr`) into its
|
|
298
283
|
// TypeScript equivalent.
|
|
299
284
|
// It handles several cases:
|
|
@@ -628,9 +613,7 @@ func (c *GoToTSCompiler) WriteSliceExpr(exp *ast.SliceExpr) error {
|
|
|
628
613
|
isString := false
|
|
629
614
|
isTypeParam := false
|
|
630
615
|
if tv != nil {
|
|
631
|
-
|
|
632
|
-
isString = true
|
|
633
|
-
}
|
|
616
|
+
isString = c.isStringType(tv)
|
|
634
617
|
if _, isTP := tv.(*types.TypeParam); isTP {
|
|
635
618
|
isTypeParam = true
|
|
636
619
|
}
|
|
@@ -755,43 +738,3 @@ func (c *GoToTSCompiler) WriteKeyValueExpr(exp *ast.KeyValueExpr) error {
|
|
|
755
738
|
}
|
|
756
739
|
return nil
|
|
757
740
|
}
|
|
758
|
-
|
|
759
|
-
// hasMapConstraint checks if an interface constraint includes map types
|
|
760
|
-
// For constraints like ~map[K]V, this returns true
|
|
761
|
-
func hasMapConstraint(iface *types.Interface) bool {
|
|
762
|
-
// Check if the interface has type terms that include map types
|
|
763
|
-
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
|
764
|
-
embedded := iface.EmbeddedType(i)
|
|
765
|
-
if union, ok := embedded.(*types.Union); ok {
|
|
766
|
-
for j := 0; j < union.Len(); j++ {
|
|
767
|
-
term := union.Term(j)
|
|
768
|
-
if _, isMap := term.Type().Underlying().(*types.Map); isMap {
|
|
769
|
-
return true
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
} else if _, isMap := embedded.Underlying().(*types.Map); isMap {
|
|
773
|
-
return true
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
return false
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// getMapValueTypeFromConstraint extracts the value type from a map constraint
|
|
780
|
-
// For constraints like ~map[K]V, this returns V
|
|
781
|
-
func getMapValueTypeFromConstraint(iface *types.Interface) types.Type {
|
|
782
|
-
// Check if the interface has type terms that include map types
|
|
783
|
-
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
|
784
|
-
embedded := iface.EmbeddedType(i)
|
|
785
|
-
if union, ok := embedded.(*types.Union); ok {
|
|
786
|
-
for j := 0; j < union.Len(); j++ {
|
|
787
|
-
term := union.Term(j)
|
|
788
|
-
if mapType, isMap := term.Type().Underlying().(*types.Map); isMap {
|
|
789
|
-
return mapType.Elem()
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
} else if mapType, isMap := embedded.Underlying().(*types.Map); isMap {
|
|
793
|
-
return mapType.Elem()
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
return nil
|
|
797
|
-
}
|
package/compiler/protobuf.go
CHANGED
|
@@ -12,29 +12,37 @@ import (
|
|
|
12
12
|
"golang.org/x/tools/go/packages"
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
15
|
+
// getProtobufMessageInterface attempts to find the protobuf-go-lite Message interface
|
|
16
|
+
// from the loaded packages in analysis. Returns nil if not found.
|
|
17
|
+
func (c *GoToTSCompiler) getProtobufMessageInterface() *types.Interface {
|
|
18
|
+
if c.analysis == nil || c.analysis.AllPackages == nil {
|
|
19
|
+
return nil
|
|
20
|
+
}
|
|
21
|
+
pkg := c.analysis.AllPackages["github.com/aperturerobotics/protobuf-go-lite"]
|
|
22
|
+
if pkg == nil || pkg.Types == nil {
|
|
23
|
+
return nil
|
|
24
|
+
}
|
|
25
|
+
obj := pkg.Types.Scope().Lookup("Message")
|
|
26
|
+
if obj == nil {
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
if tn, ok := obj.(*types.TypeName); ok {
|
|
30
|
+
if iface, ok := tn.Type().Underlying().(*types.Interface); ok {
|
|
31
|
+
return iface
|
|
35
32
|
}
|
|
36
33
|
}
|
|
37
|
-
return
|
|
34
|
+
return nil
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// typeHasMethods returns true if the given type's method set contains all the specified names.
|
|
38
|
+
func (c *GoToTSCompiler) typeHasMethods(t types.Type, names ...string) bool {
|
|
39
|
+
mset := types.NewMethodSet(t)
|
|
40
|
+
for _, name := range names {
|
|
41
|
+
if mset.Lookup(nil, name) == nil {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return true
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
// convertProtobufFieldName converts Go PascalCase field names to TypeScript camelCase
|
|
@@ -44,9 +52,11 @@ func (c *GoToTSCompiler) convertProtobufFieldName(goFieldName string) string {
|
|
|
44
52
|
return goFieldName
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
// Convert first character to lowercase
|
|
55
|
+
// Convert first character to lowercase if ASCII uppercase
|
|
48
56
|
runes := []rune(goFieldName)
|
|
49
|
-
runes[0]
|
|
57
|
+
if runes[0] >= 'A' && runes[0] <= 'Z' {
|
|
58
|
+
runes[0] = runes[0] + ('a' - 'A')
|
|
59
|
+
}
|
|
50
60
|
return string(runes)
|
|
51
61
|
}
|
|
52
62
|
|
|
@@ -119,18 +129,104 @@ func (c *PackageCompiler) copyProtobufTSFile(sourcePath, fileName string) error
|
|
|
119
129
|
|
|
120
130
|
// writeProtobufExports writes exports for a protobuf file to the index.ts file
|
|
121
131
|
func (c *PackageCompiler) writeProtobufExports(indexFile *os.File, fileName string) error {
|
|
122
|
-
// For protobuf files,
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if _, err := indexFile.WriteString(exportLine); err != nil {
|
|
132
|
+
// For protobuf files, try to parse the copied .pb.ts file in the output directory
|
|
133
|
+
// to discover exported symbols and re-export them from the index. This avoids
|
|
134
|
+
// hard-coding names like ExampleMsg and protobufPackage.
|
|
135
|
+
|
|
136
|
+
pbTsPath := filepath.Join(c.outputPath, fileName+".ts")
|
|
137
|
+
content, err := os.ReadFile(pbTsPath)
|
|
138
|
+
if err != nil {
|
|
130
139
|
return err
|
|
131
140
|
}
|
|
132
141
|
|
|
133
|
-
|
|
142
|
+
// Very simple export discovery: capture names from
|
|
143
|
+
// - export const Name
|
|
144
|
+
// - export interface Name
|
|
145
|
+
// - export class Name
|
|
146
|
+
// - export function Name
|
|
147
|
+
// We avoid type-only exports for now.
|
|
148
|
+
var exports []string
|
|
149
|
+
lines := strings.Split(string(content), "\n")
|
|
150
|
+
for _, ln := range lines {
|
|
151
|
+
l := strings.TrimSpace(ln)
|
|
152
|
+
if strings.HasPrefix(l, "export const ") {
|
|
153
|
+
rest := strings.TrimPrefix(l, "export const ")
|
|
154
|
+
name := takeIdent(rest)
|
|
155
|
+
if name != "" {
|
|
156
|
+
exports = append(exports, name)
|
|
157
|
+
}
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
if strings.HasPrefix(l, "export interface ") {
|
|
161
|
+
rest := strings.TrimPrefix(l, "export interface ")
|
|
162
|
+
name := takeIdent(rest)
|
|
163
|
+
if name != "" {
|
|
164
|
+
exports = append(exports, name)
|
|
165
|
+
}
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
if strings.HasPrefix(l, "export class ") {
|
|
169
|
+
rest := strings.TrimPrefix(l, "export class ")
|
|
170
|
+
name := takeIdent(rest)
|
|
171
|
+
if name != "" {
|
|
172
|
+
exports = append(exports, name)
|
|
173
|
+
}
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
if strings.HasPrefix(l, "export function ") {
|
|
177
|
+
rest := strings.TrimPrefix(l, "export function ")
|
|
178
|
+
name := takeIdent(rest)
|
|
179
|
+
if name != "" {
|
|
180
|
+
exports = append(exports, name)
|
|
181
|
+
}
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If nothing found, fallback to default
|
|
187
|
+
if len(exports) == 0 {
|
|
188
|
+
return fmt.Errorf("no exported symbols discovered in %s.ts while generating protobuf exports", fileName)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Deduplicate while preserving order
|
|
192
|
+
seen := map[string]bool{}
|
|
193
|
+
uniq := make([]string, 0, len(exports))
|
|
194
|
+
for _, n := range exports {
|
|
195
|
+
if !seen[n] {
|
|
196
|
+
seen[n] = true
|
|
197
|
+
uniq = append(uniq, n)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
exportLine := fmt.Sprintf("export { %s } from \"./%s.js\"\n", strings.Join(uniq, ", "), fileName)
|
|
202
|
+
_, err = indexFile.WriteString(exportLine)
|
|
203
|
+
return err
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// takeIdent extracts a leading identifier token from the beginning of s.
|
|
207
|
+
// Returns an empty string if no valid identifier is found.
|
|
208
|
+
func takeIdent(s string) string {
|
|
209
|
+
s = strings.TrimSpace(s)
|
|
210
|
+
if s == "" {
|
|
211
|
+
return ""
|
|
212
|
+
}
|
|
213
|
+
// Identifier: letter or _ followed by letters, digits, or _
|
|
214
|
+
var b strings.Builder
|
|
215
|
+
for i, r := range s {
|
|
216
|
+
if i == 0 {
|
|
217
|
+
if !(r == '_' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z') {
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
b.WriteRune(r)
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
if r == '_' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' {
|
|
224
|
+
b.WriteRune(r)
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
break
|
|
228
|
+
}
|
|
229
|
+
return b.String()
|
|
134
230
|
}
|
|
135
231
|
|
|
136
232
|
// addProtobufImports adds imports for protobuf types when .pb.ts files are present in the package
|
|
@@ -146,11 +242,59 @@ func (c *FileCompiler) addProtobufImports() error {
|
|
|
146
242
|
pbTsPath := filepath.Join(packageDir, pbTsFileName)
|
|
147
243
|
|
|
148
244
|
if _, err := os.Stat(pbTsPath); err == nil {
|
|
149
|
-
// .pb.ts file exists,
|
|
245
|
+
// .pb.ts file exists, parse it for exports and add imports accordingly
|
|
150
246
|
pbBaseName := strings.TrimSuffix(baseFileName, ".pb.go")
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
247
|
+
|
|
248
|
+
content, rerr := os.ReadFile(pbTsPath)
|
|
249
|
+
if rerr != nil {
|
|
250
|
+
return fmt.Errorf("failed to read %s for protobuf imports: %w", pbTsPath, rerr)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Discover exported identifiers (const/interface/class/function)
|
|
254
|
+
var exports []string
|
|
255
|
+
for _, ln := range strings.Split(string(content), "\n") {
|
|
256
|
+
l := strings.TrimSpace(ln)
|
|
257
|
+
if strings.HasPrefix(l, "export const ") {
|
|
258
|
+
if name := takeIdent(strings.TrimPrefix(l, "export const ")); name != "" {
|
|
259
|
+
exports = append(exports, name)
|
|
260
|
+
}
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
if strings.HasPrefix(l, "export interface ") {
|
|
264
|
+
if name := takeIdent(strings.TrimPrefix(l, "export interface ")); name != "" {
|
|
265
|
+
exports = append(exports, name)
|
|
266
|
+
}
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
if strings.HasPrefix(l, "export class ") {
|
|
270
|
+
if name := takeIdent(strings.TrimPrefix(l, "export class ")); name != "" {
|
|
271
|
+
exports = append(exports, name)
|
|
272
|
+
}
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
if strings.HasPrefix(l, "export function ") {
|
|
276
|
+
if name := takeIdent(strings.TrimPrefix(l, "export function ")); name != "" {
|
|
277
|
+
exports = append(exports, name)
|
|
278
|
+
}
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if len(exports) == 0 {
|
|
284
|
+
return fmt.Errorf("no exported symbols discovered in %s for protobuf imports", pbTsPath)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Deduplicate
|
|
288
|
+
seen := map[string]bool{}
|
|
289
|
+
uniq := make([]string, 0, len(exports))
|
|
290
|
+
for _, n := range exports {
|
|
291
|
+
if !seen[n] {
|
|
292
|
+
seen[n] = true
|
|
293
|
+
uniq = append(uniq, n)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
c.codeWriter.WriteLinef("import { %s } from \"./%s.pb.js\";", strings.Join(uniq, ", "), pbBaseName)
|
|
154
298
|
break
|
|
155
299
|
}
|
|
156
300
|
}
|