goscript 0.0.24 → 0.0.26

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 (75) hide show
  1. package/README.md +1 -1
  2. package/cmd/goscript/cmd_compile.go +17 -1
  3. package/compiler/analysis.go +1 -1
  4. package/compiler/builtin_test.go +2 -14
  5. package/compiler/compiler.go +251 -11
  6. package/compiler/compiler_test.go +60 -0
  7. package/compiler/decl.go +7 -1
  8. package/compiler/expr-call.go +212 -2
  9. package/compiler/expr.go +46 -2
  10. package/compiler/field.go +4 -4
  11. package/compiler/spec-value.go +1 -1
  12. package/compiler/stmt-range.go +204 -1
  13. package/compiler/type.go +47 -4
  14. package/dist/gs/builtin/builtin.d.ts +9 -0
  15. package/dist/gs/builtin/builtin.js +10 -1
  16. package/dist/gs/builtin/builtin.js.map +1 -1
  17. package/dist/gs/builtin/channel.d.ts +193 -0
  18. package/dist/gs/builtin/channel.js.map +1 -1
  19. package/dist/gs/builtin/defer.d.ts +38 -0
  20. package/dist/gs/builtin/defer.js.map +1 -1
  21. package/dist/gs/builtin/index.d.ts +1 -0
  22. package/dist/gs/builtin/index.js +2 -0
  23. package/dist/gs/builtin/index.js.map +1 -0
  24. package/dist/gs/builtin/io.d.ts +16 -0
  25. package/dist/gs/builtin/io.js.map +1 -1
  26. package/dist/gs/builtin/map.d.ts +33 -0
  27. package/dist/gs/builtin/map.js.map +1 -1
  28. package/dist/gs/builtin/slice.d.ts +173 -0
  29. package/dist/gs/builtin/slice.js +38 -24
  30. package/dist/gs/builtin/slice.js.map +1 -1
  31. package/dist/gs/builtin/type.d.ts +203 -0
  32. package/dist/gs/builtin/type.js +1 -2
  33. package/dist/gs/builtin/type.js.map +1 -1
  34. package/dist/gs/builtin/varRef.d.ts +14 -0
  35. package/dist/gs/builtin/varRef.js.map +1 -1
  36. package/dist/gs/cmp/index.d.ts +4 -0
  37. package/dist/gs/cmp/index.js +27 -0
  38. package/dist/gs/cmp/index.js.map +1 -0
  39. package/dist/gs/context/context.d.ts +26 -0
  40. package/dist/gs/context/context.js +297 -38
  41. package/dist/gs/context/context.js.map +1 -1
  42. package/dist/gs/context/index.d.ts +1 -0
  43. package/dist/gs/errors/errors.d.ts +8 -0
  44. package/dist/gs/errors/errors.js +190 -0
  45. package/dist/gs/errors/errors.js.map +1 -0
  46. package/dist/gs/errors/index.d.ts +1 -0
  47. package/dist/gs/errors/index.js +2 -0
  48. package/dist/gs/errors/index.js.map +1 -0
  49. package/dist/gs/internal/goarch/index.d.ts +6 -0
  50. package/dist/gs/internal/goarch/index.js +14 -0
  51. package/dist/gs/internal/goarch/index.js.map +1 -0
  52. package/dist/gs/iter/index.d.ts +1 -0
  53. package/dist/gs/iter/index.js +2 -0
  54. package/dist/gs/iter/index.js.map +1 -0
  55. package/dist/gs/iter/iter.d.ts +4 -0
  56. package/dist/gs/iter/iter.js +91 -0
  57. package/dist/gs/iter/iter.js.map +1 -0
  58. package/dist/gs/math/bits/index.d.ts +47 -0
  59. package/dist/gs/math/bits/index.js +300 -0
  60. package/dist/gs/math/bits/index.js.map +1 -0
  61. package/dist/gs/runtime/index.d.ts +1 -0
  62. package/dist/gs/runtime/runtime.d.ts +42 -0
  63. package/dist/gs/runtime/runtime.js +15 -18
  64. package/dist/gs/runtime/runtime.js.map +1 -1
  65. package/dist/gs/slices/index.d.ts +1 -0
  66. package/dist/gs/slices/index.js +2 -0
  67. package/dist/gs/slices/index.js.map +1 -0
  68. package/dist/gs/slices/slices.d.ts +8 -0
  69. package/dist/gs/slices/slices.js +20 -0
  70. package/dist/gs/slices/slices.js.map +1 -0
  71. package/dist/gs/time/index.d.ts +1 -0
  72. package/dist/gs/time/time.d.ts +57 -0
  73. package/dist/gs/time/time.js +108 -15
  74. package/dist/gs/time/time.js.map +1 -1
  75. package/package.json +3 -2
package/README.md CHANGED
@@ -95,7 +95,7 @@ func main() {
95
95
 
96
96
  // Compile the desired Go package(s)
97
97
  // Replace "." with the specific Go import path of the package you want to compile
98
- if err := comp.CompilePackages(context.Background(), "your/go/package/path"); err != nil {
98
+ if _, err := comp.CompilePackages(context.Background(), "your/go/package/path"); err != nil {
99
99
  log.Fatalf("compilation failed: %v", err)
100
100
  }
101
101
 
@@ -62,6 +62,21 @@ var CompileCommands = []*cli.Command{{
62
62
  Destination: &cliCompilerBuildFlags,
63
63
  EnvVars: []string{"GOSCRIPT_BUILD_FLAGS"},
64
64
  },
65
+ &cli.BoolFlag{
66
+ Name: "disable-emit-builtin",
67
+ Usage: "disable emitting built-in packages that have handwritten equivalents",
68
+ Destination: &cliCompilerConfig.DisableEmitBuiltin,
69
+ Value: false,
70
+ EnvVars: []string{"GOSCRIPT_DISABLE_EMIT_BUILTIN"},
71
+ },
72
+ &cli.BoolFlag{
73
+ Name: "all-dependencies",
74
+ Usage: "compile all dependencies of the requested packages",
75
+ Aliases: []string{"all-deps", "deps"},
76
+ Destination: &cliCompilerConfig.AllDependencies,
77
+ Value: false,
78
+ EnvVars: []string{"GOSCRIPT_ALL_DEPENDENCIES"},
79
+ },
65
80
  },
66
81
  }}
67
82
 
@@ -75,5 +90,6 @@ func compilePackage(c *cli.Context) error {
75
90
  // build flags
76
91
  cliCompilerConfig.BuildFlags = slices.Clone(cliCompilerBuildFlags.Value())
77
92
 
78
- return cliCompiler.CompilePackages(context.Background(), pkgs...)
93
+ _, err := cliCompiler.CompilePackages(context.Background(), pkgs...)
94
+ return err
79
95
  }
@@ -553,7 +553,7 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
553
553
  // Standalone &x doesn't directly assign, but its usage in assignments
554
554
  // or function calls determines varRefing. Assignments are handled below.
555
555
  // Function calls like foo(&x) would require different tracking if needed.
556
- // For now, we focus on assignments as per the request.
556
+ // TODO: for now, we focus on assignments
557
557
  return v
558
558
 
559
559
  case *ast.CallExpr:
@@ -37,7 +37,7 @@ func TestEmitBuiltinOption(t *testing.T) {
37
37
  }
38
38
 
39
39
  // Compile a package that depends on builtin (time)
40
- err = compiler.CompilePackages(context.Background(), "time")
40
+ _, err = compiler.CompilePackages(context.Background(), "time")
41
41
  if err != nil {
42
42
  t.Fatalf("Compilation failed: %v", err)
43
43
  }
@@ -76,23 +76,11 @@ func TestEmitBuiltinOption(t *testing.T) {
76
76
  }
77
77
 
78
78
  // Compile a package that depends on builtin (time)
79
- err = compiler.CompilePackages(context.Background(), "time")
79
+ _, err = compiler.CompilePackages(context.Background(), "time")
80
80
  if err != nil {
81
81
  t.Fatalf("Compilation failed: %v", err)
82
82
  }
83
83
 
84
- // Check that the unsafe package was copied to the output
85
- unsafePath := filepath.Join(outputDir, "@goscript/unsafe")
86
- if _, err := os.Stat(unsafePath); os.IsNotExist(err) {
87
- t.Errorf("unsafe package was not emitted when DisableEmitBuiltin=false")
88
- }
89
-
90
- // Also check for runtime package
91
- runtimePath := filepath.Join(outputDir, "@goscript/runtime")
92
- if _, err := os.Stat(runtimePath); os.IsNotExist(err) {
93
- t.Errorf("runtime package was not emitted when DisableEmitBuiltin=false")
94
- }
95
-
96
84
  // Time package should also have been emitted
97
85
  timePath := filepath.Join(outputDir, "@goscript/time")
98
86
  if _, err := os.Stat(timePath); os.IsNotExist(err) {
@@ -4,6 +4,7 @@ import (
4
4
  "context"
5
5
  "fmt"
6
6
  "go/ast"
7
+ "go/constant"
7
8
  "go/token"
8
9
  "go/types"
9
10
  "os"
@@ -72,6 +73,16 @@ func NewCompiler(conf *Config, le *logrus.Entry, opts *packages.Config) (*Compil
72
73
  return &Compiler{config: *conf, le: le, opts: *opts}, nil
73
74
  }
74
75
 
76
+ // CompilationResult contains information about what was compiled
77
+ type CompilationResult struct {
78
+ // CompiledPackages contains the package paths of all packages that were actually compiled to TypeScript
79
+ CompiledPackages []string
80
+ // CopiedPackages contains the package paths of all packages that were copied from handwritten sources
81
+ CopiedPackages []string
82
+ // OriginalPackages contains the package paths that were explicitly requested for compilation
83
+ OriginalPackages []string
84
+ }
85
+
75
86
  // CompilePackages loads Go packages based on the provided patterns and
76
87
  // then compiles each loaded package into TypeScript. It uses the context for
77
88
  // cancellation and applies the compiler's configured options during package loading.
@@ -79,7 +90,8 @@ func NewCompiler(conf *Config, le *logrus.Entry, opts *packages.Config) (*Compil
79
90
  // invokes its `Compile` method.
80
91
  // If c.config.AllDependencies is true, it will also compile all dependencies
81
92
  // of the requested packages, including standard library dependencies.
82
- func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) error {
93
+ // Returns a CompilationResult with information about what was compiled.
94
+ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*CompilationResult, error) {
83
95
  opts := c.opts
84
96
  opts.Context = ctx
85
97
 
@@ -87,7 +99,7 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
87
99
  opts.Mode |= packages.NeedImports
88
100
  pkgs, err := packages.Load(&opts, patterns...)
89
101
  if err != nil {
90
- return fmt.Errorf("failed to load packages: %w", err)
102
+ return nil, fmt.Errorf("failed to load packages: %w", err)
91
103
  }
92
104
 
93
105
  // build a list of packages that patterns matched
@@ -96,12 +108,23 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
96
108
  patternPkgPaths = append(patternPkgPaths, pkg.PkgPath)
97
109
  }
98
110
 
111
+ result := &CompilationResult{
112
+ OriginalPackages: patternPkgPaths,
113
+ }
114
+
99
115
  // If AllDependencies is true, we need to collect all dependencies
100
116
  if c.config.AllDependencies {
101
117
  // Create a set to track processed packages by their ID
102
118
  processed := make(map[string]bool)
103
119
  var allPkgs []*packages.Package
104
120
 
121
+ // Helper function to check if a package has a handwritten equivalent
122
+ hasHandwrittenEquivalent := func(pkgPath string) bool {
123
+ gsSourcePath := "gs/" + pkgPath
124
+ _, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
125
+ return gsErr == nil
126
+ }
127
+
105
128
  // Visit all packages and their dependencies
106
129
  var visit func(pkg *packages.Package)
107
130
  visit = func(pkg *packages.Package) {
@@ -109,8 +132,17 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
109
132
  return
110
133
  }
111
134
  processed[pkg.ID] = true
135
+
136
+ // Add this package to the list of all packages
112
137
  allPkgs = append(allPkgs, pkg)
113
138
 
139
+ // Check if this package has a handwritten equivalent
140
+ if hasHandwrittenEquivalent(pkg.PkgPath) {
141
+ // Add this package but don't visit its dependencies
142
+ c.le.Debugf("Skipping dependencies of handwritten package: %s", pkg.PkgPath)
143
+ return
144
+ }
145
+
114
146
  // Visit all imports, including standard library packages
115
147
  for _, imp := range pkg.Imports {
116
148
  visit(imp)
@@ -146,18 +178,31 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
146
178
  */
147
179
  }
148
180
 
181
+ // If DisableEmitBuiltin is false, we need to copy the builtin package to the output directory
182
+ if !c.config.DisableEmitBuiltin {
183
+ c.le.Infof("Copying builtin package to output directory")
184
+ builtinPath := "gs/builtin"
185
+ outputPath := ComputeModulePath(c.config.OutputPath, "builtin")
186
+ if err := c.copyEmbeddedPackage(builtinPath, outputPath); err != nil {
187
+ return nil, fmt.Errorf("failed to copy builtin package to output directory: %w", err)
188
+ }
189
+ result.CopiedPackages = append(result.CopiedPackages, "builtin")
190
+ }
191
+
149
192
  // Compile all packages
150
193
  for _, pkg := range pkgs {
151
194
  // Check if the package has a handwritten equivalent
195
+ // If the package was explicitly requested, skip this logic
152
196
  if !slices.Contains(patternPkgPaths, pkg.PkgPath) {
153
197
  gsSourcePath := "gs/" + pkg.PkgPath
154
198
  _, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
155
199
  if gsErr != nil && !os.IsNotExist(gsErr) {
156
- return gsErr
200
+ return nil, gsErr
157
201
  }
158
202
  if gsErr == nil {
159
203
  if c.config.DisableEmitBuiltin {
160
204
  c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
205
+ result.CopiedPackages = append(result.CopiedPackages, pkg.PkgPath)
161
206
  continue
162
207
  } else {
163
208
  // If DisableEmitBuiltin is false, we need to copy the handwritten package to the output directory
@@ -168,19 +213,20 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
168
213
 
169
214
  // Remove existing directory if it exists
170
215
  if err := os.RemoveAll(outputPath); err != nil {
171
- return fmt.Errorf("failed to remove existing output directory for %s: %w", pkg.PkgPath, err)
216
+ return nil, fmt.Errorf("failed to remove existing output directory for %s: %w", pkg.PkgPath, err)
172
217
  }
173
218
 
174
219
  // Create the output directory
175
220
  if err := os.MkdirAll(outputPath, 0o755); err != nil {
176
- return fmt.Errorf("failed to create output directory for %s: %w", pkg.PkgPath, err)
221
+ return nil, fmt.Errorf("failed to create output directory for %s: %w", pkg.PkgPath, err)
177
222
  }
178
223
 
179
224
  // Copy files from embedded FS to output directory
180
225
  if err := c.copyEmbeddedPackage(gsSourcePath, outputPath); err != nil {
181
- return fmt.Errorf("failed to copy embedded package %s: %w", pkg.PkgPath, err)
226
+ return nil, fmt.Errorf("failed to copy embedded package %s: %w", pkg.PkgPath, err)
182
227
  }
183
228
 
229
+ result.CopiedPackages = append(result.CopiedPackages, pkg.PkgPath)
184
230
  continue
185
231
  }
186
232
  }
@@ -192,17 +238,63 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
192
238
  continue
193
239
  }
194
240
 
241
+ // Check if this is the unsafe package, which is not supported in GoScript
242
+ if pkg.PkgPath == "unsafe" {
243
+ // Find which packages that would actually be compiled depend on unsafe
244
+ var dependentPackages []string
245
+ for _, otherPkg := range pkgs {
246
+ if otherPkg.PkgPath != "unsafe" {
247
+ // Check if this package would actually be compiled (same logic as above)
248
+ wouldBeCompiled := true
249
+
250
+ // If the package was not explicitly requested, check if it has a handwritten equivalent
251
+ if !slices.Contains(patternPkgPaths, otherPkg.PkgPath) {
252
+ gsSourcePath := "gs/" + otherPkg.PkgPath
253
+ _, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
254
+ if gsErr == nil {
255
+ // Package has handwritten equivalent, so it wouldn't be compiled
256
+ wouldBeCompiled = false
257
+ }
258
+ }
259
+
260
+ // Skip packages that failed to load
261
+ if len(otherPkg.Errors) > 0 {
262
+ wouldBeCompiled = false
263
+ }
264
+
265
+ // Only include packages that would actually be compiled and import unsafe
266
+ if wouldBeCompiled {
267
+ for importPath := range otherPkg.Imports {
268
+ if importPath == "unsafe" {
269
+ dependentPackages = append(dependentPackages, otherPkg.PkgPath)
270
+ break
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ dependentList := "unknown package"
278
+ if len(dependentPackages) > 0 {
279
+ dependentList = strings.Join(dependentPackages, ", ")
280
+ }
281
+
282
+ return nil, fmt.Errorf("cannot compile package 'unsafe': GoScript does not support the unsafe package due to its low-level memory operations that are incompatible with TypeScript/JavaScript. This package is required by: %s. Consider using alternative approaches that don't require unsafe operations", dependentList)
283
+ }
284
+
195
285
  pkgCompiler, err := NewPackageCompiler(c.le, &c.config, pkg)
196
286
  if err != nil {
197
- return fmt.Errorf("failed to create package compiler for %s: %w", pkg.PkgPath, err)
287
+ return nil, fmt.Errorf("failed to create package compiler for %s: %w", pkg.PkgPath, err)
198
288
  }
199
289
 
200
290
  if err := pkgCompiler.Compile(ctx); err != nil {
201
- return fmt.Errorf("failed to compile package %s: %w", pkg.PkgPath, err)
291
+ return nil, fmt.Errorf("failed to compile package %s: %w", pkg.PkgPath, err)
202
292
  }
293
+
294
+ result.CompiledPackages = append(result.CompiledPackages, pkg.PkgPath)
203
295
  }
204
296
 
205
- return nil
297
+ return result, nil
206
298
  }
207
299
 
208
300
  // PackageCompiler is responsible for compiling an entire Go package into
@@ -433,6 +525,7 @@ func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, analysis *Analy
433
525
  // WriteIdent translates a Go identifier (`ast.Ident`) used as a value (e.g.,
434
526
  // variable, function name) into its TypeScript equivalent.
435
527
  // - If the identifier is `nil`, it writes `null`.
528
+ // - If the identifier refers to a constant, it writes the constant's evaluated value.
436
529
  // - Otherwise, it writes the identifier's name.
437
530
  // - If `accessVarRefedValue` is true and the analysis (`c.analysis.NeedsVarRefAccess`)
438
531
  // indicates the variable is variable referenced, `.value` is appended to access the contained value.
@@ -452,8 +545,22 @@ func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessVarRefedValue bool) {
452
545
  obj = c.pkg.TypesInfo.Defs[exp]
453
546
  }
454
547
 
455
- // Write the identifier name first
456
- c.tsw.WriteLiterally(exp.Name)
548
+ // Check if this identifier refers to a constant
549
+ if obj != nil {
550
+ if constObj, isConst := obj.(*types.Const); isConst {
551
+ // Only evaluate constants from the current package being compiled
552
+ // Don't evaluate constants from imported packages (they should use their exported names)
553
+ // Special case: predeclared constants like iota have a nil package, so we should evaluate them
554
+ if constObj.Pkg() == c.pkg.Types || constObj.Pkg() == nil {
555
+ // Write the constant's evaluated value instead of the identifier name
556
+ c.writeConstantValue(constObj)
557
+ return
558
+ }
559
+ }
560
+ }
561
+
562
+ // Write the identifier name first, sanitizing if it's a reserved word
563
+ c.tsw.WriteLiterally(c.sanitizeIdentifier(exp.Name))
457
564
 
458
565
  // Determine if we need to access .value based on analysis data
459
566
  if obj != nil && accessVarRefedValue && c.analysis.NeedsVarRefAccess(obj) {
@@ -652,9 +759,142 @@ func (c *GoToTSCompiler) WriteDoc(doc *ast.CommentGroup) {
652
759
  }
653
760
  }
654
761
 
762
+ // sanitizeIdentifier checks if an identifier is a JavaScript/TypeScript reserved word
763
+ // and prefixes it with an underscore if it is. This prevents compilation errors
764
+ // when Go identifiers conflict with JS/TS keywords.
765
+ func (c *GoToTSCompiler) sanitizeIdentifier(name string) string {
766
+ // Don't sanitize boolean literals - they are valid in both Go and JS/TS
767
+ if name == "true" || name == "false" {
768
+ return name
769
+ }
770
+
771
+ // List of JavaScript/TypeScript reserved words that could conflict
772
+ reservedWords := map[string]bool{
773
+ "abstract": true,
774
+ "any": true,
775
+ "as": true,
776
+ "asserts": true,
777
+ "async": true,
778
+ "await": true,
779
+ "boolean": true,
780
+ "break": true,
781
+ "case": true,
782
+ "catch": true,
783
+ "class": true,
784
+ "const": true,
785
+ "constructor": true,
786
+ "continue": true,
787
+ "debugger": true,
788
+ "declare": true,
789
+ "default": true,
790
+ "delete": true,
791
+ "do": true,
792
+ "else": true,
793
+ "enum": true,
794
+ "export": true,
795
+ "extends": true,
796
+ "finally": true,
797
+ "for": true,
798
+ "from": true,
799
+ "function": true,
800
+ "get": true,
801
+ "if": true,
802
+ "implements": true,
803
+ "import": true,
804
+ "in": true,
805
+ "instanceof": true,
806
+ "interface": true,
807
+ "is": true,
808
+ "keyof": true,
809
+ "let": true,
810
+ "module": true,
811
+ "namespace": true,
812
+ "never": true,
813
+ "new": true,
814
+ "null": true,
815
+ "number": true,
816
+ "object": true,
817
+ "of": true,
818
+ "package": true,
819
+ "private": true,
820
+ "protected": true,
821
+ "public": true,
822
+ "readonly": true,
823
+ "require": true,
824
+ "return": true,
825
+ "set": true,
826
+ "static": true,
827
+ "string": true,
828
+ "super": true,
829
+ "switch": true,
830
+ "symbol": true,
831
+ "this": true,
832
+ "throw": true,
833
+ "try": true,
834
+ "type": true,
835
+ "typeof": true,
836
+ "undefined": true,
837
+ "unique": true,
838
+ "unknown": true,
839
+ "var": true,
840
+ "void": true,
841
+ "while": true,
842
+ "with": true,
843
+ "yield": true,
844
+ }
845
+
846
+ if reservedWords[name] {
847
+ return "_" + name
848
+ }
849
+ return name
850
+ }
851
+
852
+ // writeConstantValue writes the evaluated value of a Go constant to TypeScript.
853
+ // It handles different constant types (integer, float, string, boolean, complex)
854
+ // and writes the appropriate TypeScript literal.
855
+ func (c *GoToTSCompiler) writeConstantValue(constObj *types.Const) {
856
+ val := constObj.Val()
857
+
858
+ switch val.Kind() {
859
+ case constant.Int:
860
+ // For integer constants, write the string representation
861
+ c.tsw.WriteLiterally(val.String())
862
+ case constant.Float:
863
+ // For float constants, write the string representation
864
+ c.tsw.WriteLiterally(val.String())
865
+ case constant.String:
866
+ // For string constants, write as a quoted string literal
867
+ c.tsw.WriteLiterally(val.String()) // val.String() already includes quotes
868
+ case constant.Bool:
869
+ // For boolean constants, write true/false
870
+ if constant.BoolVal(val) {
871
+ c.tsw.WriteLiterally("true")
872
+ } else {
873
+ c.tsw.WriteLiterally("false")
874
+ }
875
+ case constant.Complex:
876
+ // For complex constants, we need to handle them specially
877
+ // For now, write as a comment indicating unsupported
878
+ c.tsw.WriteLiterally("/* complex constant: " + val.String() + " */")
879
+ default:
880
+ // For unknown constant types, write as a comment
881
+ c.tsw.WriteLiterally("/* unknown constant: " + val.String() + " */")
882
+ }
883
+ }
884
+
655
885
  // copyEmbeddedPackage recursively copies files from an embedded FS path to a filesystem directory.
656
886
  // It handles both regular files and directories.
657
887
  func (c *Compiler) copyEmbeddedPackage(embeddedPath string, outputPath string) error {
888
+ // Remove the output path if it exists
889
+ if err := os.RemoveAll(outputPath); err != nil {
890
+ return fmt.Errorf("failed to remove output directory %s: %w", outputPath, err)
891
+ }
892
+
893
+ // Create the output path
894
+ if err := os.MkdirAll(outputPath, 0o755); err != nil {
895
+ return fmt.Errorf("failed to create output directory %s: %w", outputPath, err)
896
+ }
897
+
658
898
  // List the entries in the embedded path
659
899
  entries, err := gs.GsOverrides.ReadDir(embeddedPath)
660
900
  if err != nil {
@@ -1,6 +1,7 @@
1
1
  package compiler_test
2
2
 
3
3
  import (
4
+ "context"
4
5
  "os"
5
6
  "os/exec"
6
7
  "path/filepath"
@@ -11,7 +12,9 @@ import (
11
12
  "sync/atomic"
12
13
  "testing"
13
14
 
15
+ "github.com/aperturerobotics/goscript/compiler"
14
16
  "github.com/aperturerobotics/goscript/compliance"
17
+ "github.com/sirupsen/logrus"
15
18
  )
16
19
 
17
20
  // NOTE: this is here instead of compliance/compliance_test.go so coverage ends up in this package.
@@ -137,3 +140,60 @@ func getParentGoModulePath() (string, error) {
137
140
  }
138
141
  return strings.TrimSpace(string(output)), nil
139
142
  }
143
+
144
+ func TestUnsafePackageErrorMessage(t *testing.T) {
145
+ // Create a temporary directory for the test output
146
+ tempDir, err := os.MkdirTemp("", "goscript-test-unsafe")
147
+ if err != nil {
148
+ t.Fatalf("Failed to create temp dir: %v", err)
149
+ }
150
+ defer os.RemoveAll(tempDir)
151
+
152
+ // Setup logger
153
+ log := logrus.New()
154
+ log.SetLevel(logrus.DebugLevel)
155
+ le := logrus.NewEntry(log)
156
+
157
+ // Test with AllDependencies=true to ensure we get all packages including unsafe
158
+ config := &compiler.Config{
159
+ OutputPath: tempDir,
160
+ AllDependencies: true,
161
+ DisableEmitBuiltin: true, // This ensures handwritten packages are skipped
162
+ }
163
+
164
+ comp, err := compiler.NewCompiler(config, le, nil)
165
+ if err != nil {
166
+ t.Fatalf("Failed to create compiler: %v", err)
167
+ }
168
+
169
+ // Try to compile a package that has dependencies that import unsafe
170
+ // We'll use "sync/atomic" which imports unsafe but doesn't have a handwritten equivalent
171
+ _, err = comp.CompilePackages(context.Background(), "sync/atomic")
172
+
173
+ // We expect this to fail with an unsafe package error
174
+ if err == nil {
175
+ t.Fatalf("Expected compilation to fail due to unsafe package, but it succeeded")
176
+ }
177
+
178
+ errorMsg := err.Error()
179
+
180
+ // Verify the error message contains the expected text
181
+ if !strings.Contains(errorMsg, "cannot compile package 'unsafe'") {
182
+ t.Errorf("Error message should mention unsafe package, got: %s", errorMsg)
183
+ }
184
+
185
+ // Verify that packages with handwritten equivalents are NOT mentioned in the error
186
+ // These packages have handwritten equivalents in gs/ and should not be in the error message
187
+ handwrittenPackages := []string{"runtime", "errors", "time", "context", "slices"}
188
+
189
+ for _, pkg := range handwrittenPackages {
190
+ if strings.Contains(errorMsg, pkg) {
191
+ t.Errorf("Error message should not mention handwritten package '%s', but it does. Error: %s", pkg, errorMsg)
192
+ }
193
+ }
194
+
195
+ // The error message should mention sync/atomic since it would actually be compiled
196
+ if !strings.Contains(errorMsg, "sync/atomic") {
197
+ t.Errorf("Error message should mention 'sync/atomic' as it would be compiled, got: %s", errorMsg)
198
+ }
199
+ }
package/compiler/decl.go CHANGED
@@ -37,7 +37,7 @@ func (c *GoToTSCompiler) WriteDecls(decls []ast.Decl) error {
37
37
  c.tsw.WriteLine("") // Add space after spec
38
38
  }
39
39
  default:
40
- fmt.Printf("unknown decl: %#v\n", decl)
40
+ return fmt.Errorf("unknown decl: %#v", decl)
41
41
  }
42
42
  }
43
43
  return nil
@@ -79,6 +79,12 @@ func (c *GoToTSCompiler) WriteFuncDeclAsFunction(decl *ast.FuncDecl) error {
79
79
  if obj := c.pkg.TypesInfo.Defs[decl.Name]; obj != nil {
80
80
  isAsync = c.analysis.IsAsyncFunc(obj)
81
81
  }
82
+
83
+ // Always make main function async (only in main package)
84
+ if decl.Name.Name == "main" && c.pkg.Name == "main" {
85
+ isAsync = true
86
+ }
87
+
82
88
  if isAsync {
83
89
  c.tsw.WriteLiterally("async ")
84
90
  }