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.
- package/README.md +1 -1
- package/cmd/goscript/cmd_compile.go +17 -1
- package/compiler/analysis.go +1 -1
- package/compiler/builtin_test.go +2 -14
- package/compiler/compiler.go +251 -11
- package/compiler/compiler_test.go +60 -0
- package/compiler/decl.go +7 -1
- package/compiler/expr-call.go +212 -2
- package/compiler/expr.go +46 -2
- package/compiler/field.go +4 -4
- package/compiler/spec-value.go +1 -1
- package/compiler/stmt-range.go +204 -1
- package/compiler/type.go +47 -4
- package/dist/gs/builtin/builtin.d.ts +9 -0
- package/dist/gs/builtin/builtin.js +10 -1
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/channel.d.ts +193 -0
- package/dist/gs/builtin/channel.js.map +1 -1
- package/dist/gs/builtin/defer.d.ts +38 -0
- package/dist/gs/builtin/defer.js.map +1 -1
- package/dist/gs/builtin/index.d.ts +1 -0
- package/dist/gs/builtin/index.js +2 -0
- package/dist/gs/builtin/index.js.map +1 -0
- package/dist/gs/builtin/io.d.ts +16 -0
- package/dist/gs/builtin/io.js.map +1 -1
- package/dist/gs/builtin/map.d.ts +33 -0
- package/dist/gs/builtin/map.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +173 -0
- package/dist/gs/builtin/slice.js +38 -24
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +203 -0
- package/dist/gs/builtin/type.js +1 -2
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/builtin/varRef.d.ts +14 -0
- package/dist/gs/builtin/varRef.js.map +1 -1
- package/dist/gs/cmp/index.d.ts +4 -0
- package/dist/gs/cmp/index.js +27 -0
- package/dist/gs/cmp/index.js.map +1 -0
- package/dist/gs/context/context.d.ts +26 -0
- package/dist/gs/context/context.js +297 -38
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/context/index.d.ts +1 -0
- package/dist/gs/errors/errors.d.ts +8 -0
- package/dist/gs/errors/errors.js +190 -0
- package/dist/gs/errors/errors.js.map +1 -0
- package/dist/gs/errors/index.d.ts +1 -0
- package/dist/gs/errors/index.js +2 -0
- package/dist/gs/errors/index.js.map +1 -0
- package/dist/gs/internal/goarch/index.d.ts +6 -0
- package/dist/gs/internal/goarch/index.js +14 -0
- package/dist/gs/internal/goarch/index.js.map +1 -0
- package/dist/gs/iter/index.d.ts +1 -0
- package/dist/gs/iter/index.js +2 -0
- package/dist/gs/iter/index.js.map +1 -0
- package/dist/gs/iter/iter.d.ts +4 -0
- package/dist/gs/iter/iter.js +91 -0
- package/dist/gs/iter/iter.js.map +1 -0
- package/dist/gs/math/bits/index.d.ts +47 -0
- package/dist/gs/math/bits/index.js +300 -0
- package/dist/gs/math/bits/index.js.map +1 -0
- package/dist/gs/runtime/index.d.ts +1 -0
- package/dist/gs/runtime/runtime.d.ts +42 -0
- package/dist/gs/runtime/runtime.js +15 -18
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/slices/index.d.ts +1 -0
- package/dist/gs/slices/index.js +2 -0
- package/dist/gs/slices/index.js.map +1 -0
- package/dist/gs/slices/slices.d.ts +8 -0
- package/dist/gs/slices/slices.js +20 -0
- package/dist/gs/slices/slices.js.map +1 -0
- package/dist/gs/time/index.d.ts +1 -0
- package/dist/gs/time/time.d.ts +57 -0
- package/dist/gs/time/time.js +108 -15
- package/dist/gs/time/time.js.map +1 -1
- 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
|
-
|
|
93
|
+
_, err := cliCompiler.CompilePackages(context.Background(), pkgs...)
|
|
94
|
+
return err
|
|
79
95
|
}
|
package/compiler/analysis.go
CHANGED
|
@@ -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
|
-
//
|
|
556
|
+
// TODO: for now, we focus on assignments
|
|
557
557
|
return v
|
|
558
558
|
|
|
559
559
|
case *ast.CallExpr:
|
package/compiler/builtin_test.go
CHANGED
|
@@ -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) {
|
package/compiler/compiler.go
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
456
|
-
|
|
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.
|
|
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
|
}
|