goscript 0.0.49 → 0.0.51

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.
Files changed (65) hide show
  1. package/compiler/analysis.go +1021 -561
  2. package/compiler/analysis_test.go +12 -15
  3. package/compiler/compiler.go +75 -39
  4. package/compiler/decl.go +22 -0
  5. package/compiler/expr-call-async.go +239 -40
  6. package/compiler/expr-call-type-conversion.go +6 -10
  7. package/compiler/expr-call.go +58 -27
  8. package/compiler/sanitize.go +1 -2
  9. package/compiler/spec-struct.go +3 -3
  10. package/compiler/spec-value.go +2 -2
  11. package/compiler/spec.go +66 -43
  12. package/compiler/stmt-assign.go +7 -4
  13. package/compiler/stmt-select.go +52 -1
  14. package/compiler/stmt.go +63 -5
  15. package/compiler/type.go +16 -3
  16. package/dist/gs/builtin/builtin.js.map +1 -1
  17. package/dist/gs/builtin/channel.d.ts +2 -2
  18. package/dist/gs/builtin/channel.js +12 -7
  19. package/dist/gs/builtin/channel.js.map +1 -1
  20. package/dist/gs/context/context.d.ts +16 -18
  21. package/dist/gs/context/context.js +23 -13
  22. package/dist/gs/context/context.js.map +1 -1
  23. package/dist/gs/fmt/fmt.js +3 -1
  24. package/dist/gs/fmt/fmt.js.map +1 -1
  25. package/dist/gs/reflect/type.js +5 -8
  26. package/dist/gs/reflect/type.js.map +1 -1
  27. package/dist/gs/syscall/constants.d.ts +24 -0
  28. package/dist/gs/syscall/constants.js +27 -0
  29. package/dist/gs/syscall/constants.js.map +1 -0
  30. package/dist/gs/syscall/env.d.ts +6 -0
  31. package/dist/gs/syscall/env.js +43 -0
  32. package/dist/gs/syscall/env.js.map +1 -0
  33. package/dist/gs/syscall/errors.d.ts +111 -0
  34. package/dist/gs/syscall/errors.js +547 -0
  35. package/dist/gs/syscall/errors.js.map +1 -0
  36. package/dist/gs/syscall/fs.d.ts +29 -0
  37. package/dist/gs/syscall/fs.js +53 -0
  38. package/dist/gs/syscall/fs.js.map +1 -0
  39. package/dist/gs/syscall/index.d.ts +6 -80
  40. package/dist/gs/syscall/index.js +12 -168
  41. package/dist/gs/syscall/index.js.map +1 -1
  42. package/dist/gs/syscall/rawconn.d.ts +7 -0
  43. package/dist/gs/syscall/rawconn.js +19 -0
  44. package/dist/gs/syscall/rawconn.js.map +1 -0
  45. package/dist/gs/syscall/types.d.ts +12 -0
  46. package/dist/gs/syscall/types.js +2 -0
  47. package/dist/gs/syscall/types.js.map +1 -0
  48. package/dist/gs/time/time.d.ts +2 -1
  49. package/dist/gs/time/time.js +29 -19
  50. package/dist/gs/time/time.js.map +1 -1
  51. package/gs/builtin/builtin.ts +1 -7
  52. package/gs/builtin/channel.ts +18 -12
  53. package/gs/context/context.ts +63 -45
  54. package/gs/fmt/fmt.ts +5 -1
  55. package/gs/reflect/type.ts +5 -10
  56. package/gs/syscall/constants.ts +29 -0
  57. package/gs/syscall/env.ts +47 -0
  58. package/gs/syscall/errors.ts +658 -0
  59. package/gs/syscall/fs.ts +62 -0
  60. package/gs/syscall/index.ts +12 -207
  61. package/gs/syscall/rawconn.ts +23 -0
  62. package/gs/syscall/types.ts +18 -0
  63. package/gs/time/time.ts +35 -21
  64. package/package.json +2 -2
  65. package/gs/TODO.md +0 -129
@@ -160,6 +160,7 @@ func parseAndAnalyze(t *testing.T, code string) (*Analysis, map[string]types.Obj
160
160
  Defs: make(map[*ast.Ident]types.Object),
161
161
  Uses: make(map[*ast.Ident]types.Object),
162
162
  },
163
+ Fset: fset,
163
164
  }
164
165
 
165
166
  // Type check the package
@@ -170,10 +171,8 @@ func parseAndAnalyze(t *testing.T, code string) (*Analysis, map[string]types.Obj
170
171
  }
171
172
  pkg.Types = typePkg
172
173
 
173
- // Run analysis
174
- analysis := NewAnalysis()
175
- cmap := ast.NewCommentMap(fset, file, file.Comments)
176
- AnalyzeFile(file, pkg, analysis, cmap)
174
+ // Run package-level analysis
175
+ analysis := AnalyzePackageFiles(pkg, nil)
177
176
 
178
177
  // Collect variable objects
179
178
  objects := make(map[string]types.Object)
@@ -269,18 +268,16 @@ func TestWrapperTypeDetection(t *testing.T) {
269
268
  t.Fatalf("Package has errors: %v", pkg.Errors[0])
270
269
  }
271
270
 
272
- // Run analysis on the first file
271
+ // Run package-level analysis
273
272
  if len(pkg.Syntax) == 0 {
274
273
  t.Fatal("No syntax files found")
275
274
  }
276
275
 
277
- analysis := NewAnalysis()
278
- cmap := ast.NewCommentMap(pkg.Fset, pkg.Syntax[0], pkg.Syntax[0].Comments)
279
- AnalyzeFile(pkg.Syntax[0], pkg, analysis, cmap)
276
+ analysis := AnalyzePackageFiles(pkg, nil)
280
277
 
281
- // Verify the WrapperTypes map was initialized
282
- if analysis.WrapperTypes == nil {
283
- t.Error("WrapperTypes map was not initialized")
278
+ // Verify the NamedBasicTypes map was initialized
279
+ if analysis.NamedBasicTypes == nil {
280
+ t.Error("NamedBasicTypes map was not initialized")
284
281
  }
285
282
 
286
283
  // Test some type lookups to verify wrapper type detection works
@@ -290,7 +287,7 @@ func TestWrapperTypeDetection(t *testing.T) {
290
287
  // Check if MyMode is detected as a wrapper type
291
288
  if obj := scope.Lookup("MyMode"); obj != nil {
292
289
  if typeName, ok := obj.(*types.TypeName); ok {
293
- isWrapper := analysis.IsWrapperType(typeName.Type())
290
+ isWrapper := analysis.IsNamedBasicType(typeName.Type())
294
291
  if !isWrapper {
295
292
  t.Errorf("MyMode should be detected as wrapper type, got %v", isWrapper)
296
293
  }
@@ -301,7 +298,7 @@ func TestWrapperTypeDetection(t *testing.T) {
301
298
  // Test that regular struct types are not detected as wrapper types
302
299
  if obj := scope.Lookup("MyDir"); obj != nil {
303
300
  if typeName, ok := obj.(*types.TypeName); ok {
304
- isWrapper := analysis.IsWrapperType(typeName.Type())
301
+ isWrapper := analysis.IsNamedBasicType(typeName.Type())
305
302
  if isWrapper {
306
303
  t.Errorf("MyDir should not be detected as wrapper type, got %v", isWrapper)
307
304
  }
@@ -309,13 +306,13 @@ func TestWrapperTypeDetection(t *testing.T) {
309
306
  }
310
307
  }
311
308
 
312
- t.Logf("Analysis completed successfully with %d wrapper types tracked", len(analysis.WrapperTypes))
309
+ t.Logf("Analysis completed successfully with %d named basic types tracked", len(analysis.NamedBasicTypes))
313
310
  }
314
311
 
315
312
  // TestDiscoverGsPackages verifies that the discoverEmbeddedGsPackages function
316
313
  // can find packages in the embedded gs/ directory
317
314
  func TestDiscoverGsPackages(t *testing.T) {
318
- analysis := NewAnalysis()
315
+ analysis := NewAnalysis(nil)
319
316
 
320
317
  // Test package discovery using the embedded filesystem
321
318
  packages := analysis.discoverEmbeddedGsPackages()
@@ -72,7 +72,11 @@ func NewCompiler(conf *Config, le *logrus.Entry, opts *packages.Config) (*Compil
72
72
  packages.NeedTypesInfo |
73
73
  packages.NeedTypesSizes
74
74
 
75
- return &Compiler{config: *conf, le: le, opts: *opts}, nil
75
+ return &Compiler{
76
+ config: *conf,
77
+ le: le,
78
+ opts: *opts,
79
+ }, nil
76
80
  }
77
81
 
78
82
  // CompilationResult contains information about what was compiled
@@ -141,7 +145,6 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
141
145
  // Check if this package has a handwritten equivalent
142
146
  if hasHandwrittenEquivalent(pkg.PkgPath) {
143
147
  // Add this package but don't visit its dependencies
144
- // c.le.Debugf("Skipping dependencies of handwritten package: %s", pkg.PkgPath)
145
148
  return
146
149
  }
147
150
 
@@ -149,13 +152,11 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
149
152
  for _, imp := range pkg.Imports {
150
153
  // Skip protobuf-go-lite packages and their dependencies
151
154
  if isProtobufGoLitePackage(imp.PkgPath) {
152
- // c.le.Debugf("Skipping protobuf-go-lite package: %s", imp.PkgPath)
153
155
  continue
154
156
  }
155
157
 
156
158
  // Skip packages that are only used by .pb.go files
157
159
  if isPackageOnlyUsedByProtobufFiles(pkg, imp.PkgPath) {
158
- // c.le.Debugf("Skipping package only used by .pb.go files: %s", imp.PkgPath)
159
160
  continue
160
161
  }
161
162
 
@@ -168,28 +169,39 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
168
169
  visit(pkg)
169
170
  }
170
171
 
171
- // Replace pkgs with all packages
172
- pkgs = allPkgs
172
+ // Now we have collected all dependencies, but they only have minimal information.
173
+ // We need to reload them with complete type information for compilation.
174
+ // Collect all package paths from the dependency graph
175
+ var pkgPaths []string
176
+ pkgPathSet := make(map[string]bool) // Use set to avoid duplicates
173
177
 
174
- /*
175
- // Now load all packages with full mode to get complete type information
176
- var pkgPaths []string
177
- for _, pkg := range pkgs {
178
- if pkg.PkgPath != "" {
179
- pkgPaths = append(pkgPaths, pkg.PkgPath)
180
- }
178
+ for _, pkg := range allPkgs {
179
+ if pkg.PkgPath != "" && !pkgPathSet[pkg.PkgPath] {
180
+ pkgPaths = append(pkgPaths, pkg.PkgPath)
181
+ pkgPathSet[pkg.PkgPath] = true
181
182
  }
183
+ }
184
+
185
+ // Reload all collected packages with complete type information
186
+ if len(pkgPaths) > 0 {
187
+ c.le.Debugf("Reloading %d packages with complete type information", len(pkgPaths))
182
188
 
183
- // Reload all packages with full mode
184
- // TODO: Can we get rid of this? This would be very slow!
185
189
  fullOpts := c.opts
186
190
  fullOpts.Context = ctx
191
+ // Use LoadAllSyntax to get complete type information, syntax trees, and type checking
187
192
  fullOpts.Mode = packages.LoadAllSyntax
188
- pkgs, err = packages.Load(&fullOpts, pkgPaths...)
193
+
194
+ reloadedPkgs, err := packages.Load(&fullOpts, pkgPaths...)
189
195
  if err != nil {
190
- return fmt.Errorf("failed to reload packages with full mode: %w", err)
196
+ return nil, fmt.Errorf("failed to reload packages with complete type information: %w", err)
191
197
  }
192
- */
198
+
199
+ // Replace the minimal packages with the fully loaded ones
200
+ pkgs = reloadedPkgs
201
+ } else {
202
+ // No packages to reload, use the original set
203
+ pkgs = allPkgs
204
+ }
193
205
  }
194
206
 
195
207
  // If DisableEmitBuiltin is false, we need to copy the builtin package to the output directory
@@ -206,6 +218,12 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
206
218
  // Track which gs packages have been processed to avoid duplicates
207
219
  processedGsPackages := make(map[string]bool)
208
220
 
221
+ // Create a map of all loaded packages for dependency analysis
222
+ allPackages := make(map[string]*packages.Package)
223
+ for _, pkg := range pkgs {
224
+ allPackages[pkg.PkgPath] = pkg
225
+ }
226
+
209
227
  // Compile all packages
210
228
  for _, pkg := range pkgs {
211
229
  // Check if the package has a handwritten equivalent
@@ -237,7 +255,7 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
237
255
  continue
238
256
  }
239
257
 
240
- pkgCompiler, err := NewPackageCompiler(c.le, &c.config, pkg)
258
+ pkgCompiler, err := NewPackageCompiler(c.le, &c.config, pkg, allPackages)
241
259
  if err != nil {
242
260
  return nil, fmt.Errorf("failed to create package compiler for %s: %w", pkg.PkgPath, err)
243
261
  }
@@ -262,6 +280,7 @@ type PackageCompiler struct {
262
280
  compilerConf *Config
263
281
  outputPath string
264
282
  pkg *packages.Package
283
+ allPackages map[string]*packages.Package
265
284
  }
266
285
 
267
286
  // NewPackageCompiler creates a new `PackageCompiler` for a given Go package.
@@ -272,12 +291,14 @@ func NewPackageCompiler(
272
291
  le *logrus.Entry,
273
292
  compilerConf *Config,
274
293
  pkg *packages.Package,
294
+ allPackages map[string]*packages.Package,
275
295
  ) (*PackageCompiler, error) {
276
296
  res := &PackageCompiler{
277
297
  le: le,
278
298
  pkg: pkg,
279
299
  compilerConf: compilerConf,
280
300
  outputPath: ComputeModulePath(compilerConf.OutputPath, pkg.PkgPath),
301
+ allPackages: allPackages,
281
302
  }
282
303
 
283
304
  return res, nil
@@ -304,7 +325,10 @@ func (c *PackageCompiler) Compile(ctx context.Context) error {
304
325
  }
305
326
 
306
327
  // Perform package-level analysis for auto-imports
307
- packageAnalysis := AnalyzePackage(c.pkg)
328
+ packageAnalysis := AnalyzePackageImports(c.pkg)
329
+
330
+ // Perform comprehensive package-level analysis for code generation
331
+ analysis := AnalyzePackageFiles(c.pkg, c.allPackages)
308
332
 
309
333
  // Track all compiled files for later generating the index.ts
310
334
  compiledFiles := make([]string, 0, len(c.pkg.CompiledGoFiles))
@@ -342,7 +366,7 @@ func (c *PackageCompiler) Compile(ctx context.Context) error {
342
366
 
343
367
  // log just the filename
344
368
  c.le.Debugf("GS: %s", filepath.Base(fileName))
345
- if err := c.CompileFile(ctx, fileName, f, packageAnalysis); err != nil {
369
+ if err := c.CompileFile(ctx, fileName, f, analysis, packageAnalysis); err != nil {
346
370
  return err
347
371
  }
348
372
 
@@ -392,6 +416,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
392
416
  // Find which symbols this file exports
393
417
  var valueSymbols []string
394
418
  var typeSymbols []string
419
+ var structSymbols []string // New: Track structs separately
395
420
 
396
421
  // Find the corresponding syntax file
397
422
  for i, syntax := range c.pkg.Syntax {
@@ -408,21 +433,27 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
408
433
  switch d := decl.(type) {
409
434
  case *ast.FuncDecl:
410
435
  if d.Recv == nil && d.Name.IsExported() {
411
- valueSymbols = append(valueSymbols, d.Name.Name)
436
+ valueSymbols = append(valueSymbols, sanitizeIdentifier(d.Name.Name))
412
437
  }
413
438
  case *ast.GenDecl:
414
439
  for _, spec := range d.Specs {
415
440
  switch s := spec.(type) {
416
441
  case *ast.TypeSpec:
417
442
  if s.Name.IsExported() {
418
- // All type declarations (interfaces, structs, type definitions, type aliases)
419
- // become TypeScript types and must be exported with "export type"
420
- typeSymbols = append(typeSymbols, s.Name.Name)
443
+ // Check if this is a struct type
444
+ if _, isStruct := s.Type.(*ast.StructType); isStruct {
445
+ // Structs become TypeScript classes and need both type and value exports
446
+ structSymbols = append(structSymbols, sanitizeIdentifier(s.Name.Name))
447
+ } else {
448
+ // Other type declarations (interfaces, type definitions, type aliases)
449
+ // become TypeScript types and must be exported with "export type"
450
+ typeSymbols = append(typeSymbols, sanitizeIdentifier(s.Name.Name))
451
+ }
421
452
  }
422
453
  case *ast.ValueSpec:
423
454
  for _, name := range s.Names {
424
455
  if name.IsExported() {
425
- valueSymbols = append(valueSymbols, name.Name)
456
+ valueSymbols = append(valueSymbols, sanitizeIdentifier(name.Name))
426
457
  }
427
458
  }
428
459
  }
@@ -442,6 +473,17 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
442
473
  }
443
474
  }
444
475
 
476
+ // Write struct exports (both as types and values)
477
+ if len(structSymbols) > 0 {
478
+ sort.Strings(structSymbols)
479
+ // Export classes as values (which makes them available as both types and values in TypeScript)
480
+ exportLine := fmt.Sprintf("export { %s } from \"./%s.js\"\n",
481
+ strings.Join(structSymbols, ", "), fileName)
482
+ if _, err := indexFile.WriteString(exportLine); err != nil {
483
+ return err
484
+ }
485
+ }
486
+
445
487
  if len(typeSymbols) > 0 {
446
488
  sort.Strings(typeSymbols)
447
489
  exportLine := fmt.Sprintf("export type { %s } from \"./%s.js\"\n",
@@ -456,21 +498,11 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
456
498
  }
457
499
 
458
500
  // CompileFile handles the compilation of a single Go source file to TypeScript.
459
- // It first performs a pre-compilation analysis of the file using `AnalyzeFile`
460
- // to gather information necessary for accurate TypeScript generation (e.g.,
461
- // about varRefing, async functions, defer statements).
501
+ // It uses the pre-computed package-level analysis for accurate TypeScript generation
502
+ // (e.g., about varRefing, async functions, defer statements, receiver usage across files).
462
503
  // Then, it creates a `FileCompiler` instance for the file and invokes its
463
504
  // `Compile` method to generate the TypeScript code.
464
- func (p *PackageCompiler) CompileFile(ctx context.Context, name string, syntax *ast.File, packageAnalysis *PackageAnalysis) error {
465
- // Create a new analysis instance for per-file data
466
- analysis := NewAnalysis()
467
-
468
- // Create comment map for the file
469
- cmap := ast.NewCommentMap(p.pkg.Fset, syntax, syntax.Comments)
470
-
471
- // Analyze the file before compiling
472
- AnalyzeFile(syntax, p.pkg, analysis, cmap)
473
-
505
+ func (p *PackageCompiler) CompileFile(ctx context.Context, name string, syntax *ast.File, analysis *Analysis, packageAnalysis *PackageAnalysis) error {
474
506
  fileCompiler, err := NewFileCompiler(p.compilerConf, p.pkg, syntax, name, analysis, packageAnalysis)
475
507
  if err != nil {
476
508
  return err
@@ -644,6 +676,10 @@ type GoToTSCompiler struct {
644
676
  pkg *packages.Package
645
677
 
646
678
  analysis *Analysis
679
+
680
+ // awaitedCalls tracks which call expressions have already been processed
681
+ // to avoid adding double await keywords
682
+ awaitedCalls map[*ast.CallExpr]bool
647
683
  }
648
684
 
649
685
  // It initializes the compiler with a `TSCodeWriter` for output,
package/compiler/decl.go CHANGED
@@ -3,6 +3,7 @@ package compiler
3
3
  import (
4
4
  "fmt"
5
5
  "go/ast"
6
+ "go/types"
6
7
  )
7
8
 
8
9
  // WriteDecls iterates through a slice of Go top-level declarations (`ast.Decl`)
@@ -193,6 +194,27 @@ func (c *GoToTSCompiler) writeMethodSignature(decl *ast.FuncDecl) (bool, error)
193
194
  var isAsync bool
194
195
  if obj := c.pkg.TypesInfo.Defs[decl.Name]; obj != nil {
195
196
  isAsync = c.analysis.IsAsyncFunc(obj)
197
+
198
+ // Check if this method must be async due to interface constraints
199
+ if !isAsync && decl.Recv != nil && len(decl.Recv.List) > 0 {
200
+ // Get the receiver type
201
+ receiverType := decl.Recv.List[0].Type
202
+ if starExpr, ok := receiverType.(*ast.StarExpr); ok {
203
+ receiverType = starExpr.X
204
+ }
205
+
206
+ if ident, ok := receiverType.(*ast.Ident); ok {
207
+ // Get the named type for this receiver
208
+ if receiverObj := c.pkg.TypesInfo.Uses[ident]; receiverObj != nil {
209
+ if namedType, ok := receiverObj.Type().(*types.Named); ok {
210
+ // Check if this method must be async due to interface constraints
211
+ if c.analysis.MustBeAsyncDueToInterface(namedType, decl.Name.Name) {
212
+ isAsync = true
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
196
218
  }
197
219
 
198
220
  // Methods are typically public in the TS output
@@ -2,74 +2,273 @@ package compiler
2
2
 
3
3
  import (
4
4
  "go/ast"
5
+ "go/token"
5
6
  "go/types"
6
7
  "strings"
7
8
  )
8
9
 
9
- // writeAsyncCall writes the await prefix for async function calls
10
- func (c *GoToTSCompiler) writeAsyncCall(exp *ast.CallExpr, funIdent *ast.Ident) bool {
11
- if funIdent == nil {
12
- return false
10
+ // writeAsyncCallIfNeeded writes the await prefix for async function or method calls
11
+ // Returns true if await was written, false otherwise
12
+ func (c *GoToTSCompiler) writeAsyncCallIfNeeded(exp *ast.CallExpr) bool {
13
+ // Track if we've already processed this call expression to avoid double await
14
+ if c.awaitedCalls == nil {
15
+ c.awaitedCalls = make(map[*ast.CallExpr]bool)
13
16
  }
14
17
 
15
- // Check if this is an async function call
16
- if obj := c.pkg.TypesInfo.Uses[funIdent]; obj != nil && c.analysis.IsAsyncFunc(obj) {
17
- c.tsw.WriteLiterally("await ")
18
- return true
18
+ if c.awaitedCalls[exp] {
19
+ return false // Already processed this call
19
20
  }
21
+ switch fun := exp.Fun.(type) {
22
+ case *ast.Ident:
23
+ // Function call (e.g., func())
24
+ if obj := c.pkg.TypesInfo.Uses[fun]; obj != nil && c.analysis.IsAsyncFunc(obj) {
25
+ c.awaitedCalls[exp] = true
26
+ c.tsw.WriteLiterally("await ")
27
+ return true
28
+ }
29
+ return false
20
30
 
21
- return false
22
- }
31
+ case *ast.SelectorExpr:
32
+ // Method call (e.g., obj.method())
33
+ var ident *ast.Ident
34
+ var identOk bool
35
+
36
+ // Handle both direct identifiers and pointer dereferences
37
+ switch x := fun.X.(type) {
38
+ case *ast.Ident:
39
+ ident, identOk = x, true
40
+ case *ast.StarExpr:
41
+ // Handle pointer dereference: (*p).method() or p.method() where p is a pointer
42
+ if id, isIdent := x.X.(*ast.Ident); isIdent {
43
+ ident, identOk = id, true
44
+ }
45
+ default:
46
+ return false
47
+ }
48
+
49
+ if !identOk {
50
+ return false
51
+ }
52
+
53
+ // Get the type of the receiver
54
+ obj := c.pkg.TypesInfo.Uses[ident]
55
+ if obj == nil {
56
+ return false
57
+ }
58
+
59
+ varObj, ok := obj.(*types.Var)
60
+ if !ok {
61
+ return false
62
+ }
63
+
64
+ // Get the type name and package
65
+ var namedType *types.Named
66
+ var namedTypeOk bool
67
+
68
+ // Handle both direct named types and pointer to named types
69
+ switch t := varObj.Type().(type) {
70
+ case *types.Named:
71
+ namedType, namedTypeOk = t, true
72
+ case *types.Pointer:
73
+ if nt, isNamed := t.Elem().(*types.Named); isNamed {
74
+ namedType, namedTypeOk = nt, true
75
+ }
76
+ }
77
+
78
+ if !namedTypeOk {
79
+ return false
80
+ }
81
+
82
+ typeName := namedType.Obj().Name()
83
+ methodName := fun.Sel.Name
23
84
 
24
- // writeAsyncMethodCall writes the await prefix for async method calls
25
- func (c *GoToTSCompiler) writeAsyncMethodCall(exp *ast.CallExpr) bool {
26
- selExpr, ok := exp.Fun.(*ast.SelectorExpr)
27
- if !ok {
85
+ // Check if the type is from an imported package
86
+ typePkg := namedType.Obj().Pkg()
87
+ if typePkg != nil && typePkg != c.pkg.Types {
88
+ // Use the full package path from the type information (not just the package name)
89
+ pkgPath := typePkg.Path()
90
+
91
+ // Check if this method is async based on metadata
92
+ if c.analysis.IsMethodAsync(pkgPath, typeName, methodName) {
93
+ c.awaitedCalls[exp] = true
94
+ c.tsw.WriteLiterally("await ")
95
+ return true
96
+ }
97
+ } else {
98
+ // For local types, check if the method contains async operations at runtime
99
+ // to avoid circular dependencies during analysis
100
+ if c.isLocalMethodAsync(namedType, methodName) {
101
+ c.awaitedCalls[exp] = true
102
+ c.tsw.WriteLiterally("await ")
103
+ return true
104
+ }
105
+ }
28
106
  return false
29
- }
30
107
 
31
- // Check if this is a method call on a variable (e.g., mu.Lock())
32
- ident, ok := selExpr.X.(*ast.Ident)
33
- if !ok {
108
+ default:
34
109
  return false
35
110
  }
111
+ }
36
112
 
37
- // Get the type of the receiver
38
- obj := c.pkg.TypesInfo.Uses[ident]
39
- if obj == nil {
40
- return false
113
+ // isLocalMethodAsync checks if a local method contains async operations by inspecting its body
114
+ func (c *GoToTSCompiler) isLocalMethodAsync(namedType *types.Named, methodName string) bool {
115
+ // Find the method in the named type
116
+ for i := 0; i < namedType.NumMethods(); i++ {
117
+ method := namedType.Method(i)
118
+ if method.Name() == methodName {
119
+ // Find the method declaration in the AST
120
+ methodDecl := c.findMethodDecl(namedType, methodName)
121
+ if methodDecl != nil && methodDecl.Body != nil {
122
+ // Check if the method body contains async operations
123
+ return c.containsAsyncOperationsRuntime(methodDecl.Body)
124
+ }
125
+ break
126
+ }
127
+ }
128
+ return false
129
+ }
130
+
131
+ // findMethodDecl finds the AST declaration for a method on a named type
132
+ func (c *GoToTSCompiler) findMethodDecl(namedType *types.Named, methodName string) *ast.FuncDecl {
133
+ // Search through all files in the package for the method declaration
134
+ for _, file := range c.pkg.Syntax {
135
+ for _, decl := range file.Decls {
136
+ if funcDecl, ok := decl.(*ast.FuncDecl); ok {
137
+ // Check if this is a method with the right name
138
+ if funcDecl.Name.Name == methodName && funcDecl.Recv != nil {
139
+ // Check if the receiver type matches
140
+ if c.isReceiverOfType(funcDecl, namedType) {
141
+ return funcDecl
142
+ }
143
+ }
144
+ }
145
+ }
41
146
  }
147
+ return nil
148
+ }
42
149
 
43
- varObj, ok := obj.(*types.Var)
44
- if !ok {
150
+ // isReceiverOfType checks if a function declaration is a method of the given named type
151
+ func (c *GoToTSCompiler) isReceiverOfType(funcDecl *ast.FuncDecl, namedType *types.Named) bool {
152
+ if funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 {
45
153
  return false
46
154
  }
47
155
 
48
- // Get the type name and package
49
- namedType, ok := varObj.Type().(*types.Named)
50
- if !ok {
156
+ recvField := funcDecl.Recv.List[0]
157
+ if len(recvField.Names) == 0 {
51
158
  return false
52
159
  }
53
160
 
54
- typeName := namedType.Obj().Name()
55
- methodName := selExpr.Sel.Name
161
+ recvIdent := recvField.Names[0]
162
+ if obj := c.pkg.TypesInfo.Defs[recvIdent]; obj != nil {
163
+ if varObj, ok := obj.(*types.Var); ok {
164
+ // Handle both direct named types and pointer to named types
165
+ var receiverNamedType *types.Named
166
+ switch t := varObj.Type().(type) {
167
+ case *types.Named:
168
+ receiverNamedType = t
169
+ case *types.Pointer:
170
+ if nt, isNamed := t.Elem().(*types.Named); isNamed {
171
+ receiverNamedType = nt
172
+ }
173
+ }
56
174
 
57
- // Check if the type is from an imported package
58
- typePkg := namedType.Obj().Pkg()
59
- if typePkg == nil || typePkg == c.pkg.Types {
60
- return false
175
+ if receiverNamedType != nil {
176
+ // For generic types, compare the origin types and names
177
+ // since instantiated generics have different type objects
178
+ targetObj := namedType.Obj()
179
+ receiverObj := receiverNamedType.Obj()
180
+
181
+ // Compare by name and package path
182
+ return targetObj.Name() == receiverObj.Name() &&
183
+ targetObj.Pkg() == receiverObj.Pkg()
184
+ }
185
+ }
61
186
  }
62
187
 
63
- // Use the actual package name from the type information
64
- pkgName := typePkg.Name()
188
+ return false
189
+ }
190
+
191
+ // containsAsyncOperationsRuntime checks if a node contains async operations at runtime
192
+ func (c *GoToTSCompiler) containsAsyncOperationsRuntime(node ast.Node) bool {
193
+ var hasAsync bool
194
+
195
+ ast.Inspect(node, func(n ast.Node) bool {
196
+ if n == nil {
197
+ return false
198
+ }
199
+
200
+ switch s := n.(type) {
201
+ case *ast.SendStmt:
202
+ // Channel send operation (ch <- value)
203
+ hasAsync = true
204
+ return false
205
+
206
+ case *ast.UnaryExpr:
207
+ // Channel receive operation (<-ch)
208
+ if s.Op == token.ARROW {
209
+ hasAsync = true
210
+ return false
211
+ }
212
+
213
+ case *ast.SelectStmt:
214
+ // Select statement with channel operations
215
+ hasAsync = true
216
+ return false
217
+
218
+ case *ast.CallExpr:
219
+ // Check if we're calling a function known to be async
220
+ if funcIdent, ok := s.Fun.(*ast.Ident); ok {
221
+ // Get the object for this function call
222
+ if obj := c.pkg.TypesInfo.Uses[funcIdent]; obj != nil && c.analysis.IsAsyncFunc(obj) {
223
+ hasAsync = true
224
+ return false
225
+ }
226
+ }
227
+
228
+ // Check for method calls on imported types (similar to analysis.go logic)
229
+ if selExpr, ok := s.Fun.(*ast.SelectorExpr); ok {
230
+ if ident, ok := selExpr.X.(*ast.Ident); ok {
231
+ if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
232
+ if varObj, ok := obj.(*types.Var); ok {
233
+ var namedType *types.Named
234
+ switch t := varObj.Type().(type) {
235
+ case *types.Named:
236
+ namedType = t
237
+ case *types.Pointer:
238
+ if nt, isNamed := t.Elem().(*types.Named); isNamed {
239
+ namedType = nt
240
+ }
241
+ }
242
+
243
+ if namedType != nil {
244
+ typeName := namedType.Obj().Name()
245
+ methodName := selExpr.Sel.Name
246
+
247
+ // Check if the type is from an imported package
248
+ if typePkg := namedType.Obj().Pkg(); typePkg != nil && typePkg != c.pkg.Types {
249
+ pkgPath := typePkg.Path()
250
+ if c.analysis.IsMethodAsync(pkgPath, typeName, methodName) {
251
+ hasAsync = true
252
+ return false
253
+ }
254
+ } else {
255
+ // For local types, recursively check
256
+ if c.isLocalMethodAsync(namedType, methodName) {
257
+ hasAsync = true
258
+ return false
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
65
267
 
66
- // Check if this method is async based on metadata
67
- if c.analysis.IsMethodAsync(pkgName, typeName, methodName) {
68
- c.tsw.WriteLiterally("await ")
69
268
  return true
70
- }
269
+ })
71
270
 
72
- return false
271
+ return hasAsync
73
272
  }
74
273
 
75
274
  // addNonNullAssertion adds ! for function calls that might return null