goscript 0.0.23 → 0.0.24

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 (53) hide show
  1. package/README.md +1 -1
  2. package/cmd/goscript/cmd_compile.go +1 -1
  3. package/compiler/analysis.go +73 -131
  4. package/compiler/analysis_test.go +220 -0
  5. package/compiler/assignment.go +37 -43
  6. package/compiler/builtin_test.go +102 -0
  7. package/compiler/compiler.go +79 -14
  8. package/compiler/composite-lit.go +108 -43
  9. package/compiler/config.go +7 -3
  10. package/compiler/config_test.go +6 -33
  11. package/compiler/expr-selector.go +66 -41
  12. package/compiler/expr-star.go +57 -65
  13. package/compiler/expr-type.go +1 -1
  14. package/compiler/expr-value.go +1 -1
  15. package/compiler/expr.go +79 -18
  16. package/compiler/primitive.go +11 -10
  17. package/compiler/spec-struct.go +3 -3
  18. package/compiler/spec-value.go +75 -29
  19. package/compiler/spec.go +9 -3
  20. package/compiler/stmt-assign.go +36 -2
  21. package/compiler/stmt-for.go +11 -0
  22. package/compiler/stmt-range.go +110 -0
  23. package/compiler/stmt.go +52 -0
  24. package/compiler/type.go +36 -11
  25. package/dist/gs/builtin/builtin.js +37 -0
  26. package/dist/gs/builtin/builtin.js.map +1 -0
  27. package/dist/gs/builtin/channel.js +471 -0
  28. package/dist/gs/builtin/channel.js.map +1 -0
  29. package/dist/gs/builtin/defer.js +54 -0
  30. package/dist/gs/builtin/defer.js.map +1 -0
  31. package/dist/gs/builtin/io.js +15 -0
  32. package/dist/gs/builtin/io.js.map +1 -0
  33. package/dist/gs/builtin/map.js +44 -0
  34. package/dist/gs/builtin/map.js.map +1 -0
  35. package/dist/gs/builtin/slice.js +799 -0
  36. package/dist/gs/builtin/slice.js.map +1 -0
  37. package/dist/gs/builtin/type.js +745 -0
  38. package/dist/gs/builtin/type.js.map +1 -0
  39. package/dist/gs/builtin/varRef.js +14 -0
  40. package/dist/gs/builtin/varRef.js.map +1 -0
  41. package/dist/gs/context/context.js +55 -0
  42. package/dist/gs/context/context.js.map +1 -0
  43. package/dist/gs/context/index.js +2 -0
  44. package/dist/gs/context/index.js.map +1 -0
  45. package/dist/gs/runtime/index.js +2 -0
  46. package/dist/gs/runtime/index.js.map +1 -0
  47. package/dist/gs/runtime/runtime.js +158 -0
  48. package/dist/gs/runtime/runtime.js.map +1 -0
  49. package/dist/gs/time/index.js +2 -0
  50. package/dist/gs/time/index.js.map +1 -0
  51. package/dist/gs/time/time.js +115 -0
  52. package/dist/gs/time/time.js.map +1 -0
  53. package/package.json +3 -2
@@ -21,7 +21,7 @@ import (
21
21
  // to `$.mapSet(myMap_ts, key_ts, value_ts)`.
22
22
  // - Other single-variable assignments (`variable = value`):
23
23
  // - The LHS expression is written (caller typically ensures `.value` is appended
24
- // if assigning to a boxed variable's content).
24
+ // if assigning to a VarRefed variable's content).
25
25
  // - The Go assignment token (`tok`, e.g., `=`, `+=`) is translated to its
26
26
  // TypeScript equivalent using `TokenToTs`.
27
27
  // - The RHS expression(s) are written. If `shouldApplyClone` indicates the RHS
@@ -68,10 +68,10 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
68
68
  return nil
69
69
  }
70
70
 
71
- // Special handling for boxed variables in declarations
71
+ // Special handling for variable referenced variables in declarations
72
72
  if addDeclaration && tok == token.DEFINE {
73
- // Determine if LHS is boxed
74
- isLHSBoxed := false
73
+ // Determine if LHS is variable referenced
74
+ isLHSVarRefed := false
75
75
  var lhsIdent *ast.Ident
76
76
  var lhsObj types.Object
77
77
 
@@ -84,30 +84,22 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
84
84
  lhsObj = def
85
85
  }
86
86
 
87
- // Check if this variable needs to be boxed
88
- if lhsObj != nil && c.analysis.NeedsBoxed(lhsObj) {
89
- isLHSBoxed = true
87
+ // Check if this variable needs to be variable referenced
88
+ if lhsObj != nil && c.analysis.NeedsVarRef(lhsObj) {
89
+ isLHSVarRefed = true
90
90
  }
91
91
  }
92
92
 
93
- // Special handling for short declaration of boxed variables
94
- if isLHSBoxed && lhsIdent != nil {
93
+ // Special handling for short declaration of variable referenced variables
94
+ if isLHSVarRefed && lhsIdent != nil {
95
95
  c.tsw.WriteLiterally("let ")
96
96
  // Just write the identifier name without .value
97
97
  c.tsw.WriteLiterally(lhsIdent.Name)
98
-
99
- // Add type annotation for boxed variables in declarations
100
- if lhsObj != nil {
101
- c.tsw.WriteLiterally(": ")
102
- c.tsw.WriteLiterally("$.Box<")
103
- c.WriteGoType(lhsObj.Type(), GoTypeContextGeneral)
104
- c.tsw.WriteLiterally(">")
105
- }
106
-
98
+ // No type annotation, allow TypeScript to infer it from varRef.
107
99
  c.tsw.WriteLiterally(" = ")
108
100
 
109
- // Box the initializer
110
- c.tsw.WriteLiterally("$.box(")
101
+ // Create the variable reference for the initializer
102
+ c.tsw.WriteLiterally("$.varRef(")
111
103
  if err := c.WriteValueExpr(rhs[0]); err != nil {
112
104
  return err
113
105
  }
@@ -226,12 +218,12 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
226
218
  }
227
219
 
228
220
  if !currentIsMapIndex {
229
- // For single assignments, handle boxed variables specially
221
+ // For single assignments, handle variable referenced variables specially
230
222
  if len(lhs) == 1 && len(rhs) == 1 {
231
223
  lhsExprIdent, lhsExprIsIdent := l.(*ast.Ident)
232
224
  if lhsExprIsIdent {
233
- // Determine if LHS is boxed
234
- isLHSBoxed := false
225
+ // Determine if LHS is variable referenced
226
+ isLHSVarRefed := false
235
227
  var lhsObj types.Object
236
228
 
237
229
  // Get the types.Object from the identifier
@@ -241,13 +233,13 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
241
233
  lhsObj = def
242
234
  }
243
235
 
244
- // Check if this variable needs to be boxed
245
- if lhsObj != nil && c.analysis.NeedsBoxed(lhsObj) {
246
- isLHSBoxed = true
236
+ // Check if this variable needs to be variable referenced
237
+ if lhsObj != nil && c.analysis.NeedsVarRef(lhsObj) {
238
+ isLHSVarRefed = true
247
239
  }
248
240
 
249
- // prevent writing .value unless lhs is boxed
250
- c.WriteIdent(lhsExprIdent, isLHSBoxed)
241
+ // prevent writing .value unless lhs is variable referenced
242
+ c.WriteIdent(lhsExprIdent, isLHSVarRefed)
251
243
  continue
252
244
  }
253
245
  }
@@ -279,15 +271,16 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
279
271
  if i != 0 {
280
272
  c.tsw.WriteLiterally(", ")
281
273
  }
282
- // Check if we need to access a boxed source value and apply clone
274
+
275
+ // Check if we need to access a variable referenced source value and apply clone
283
276
  // For struct value assignments, we need to handle:
284
- // 1. Unboxed source, unboxed target: source.clone()
285
- // 2. Boxed source, unboxed target: source.value.clone()
286
- // 3. Unboxed source, boxed target: $.box(source)
287
- // 4. Boxed source, boxed target: source (straight assignment of the box)
277
+ // 1. UnVarRefed source, unVarRefed target: source.clone()
278
+ // 2. Variable referenced source, unVarRefed target: source.value.clone()
279
+ // 3. UnVarRefed source, variable referenced target: $.varRef(source)
280
+ // 4. Variable referenced source, variable referenced target: source (straight assignment of the variable reference)
288
281
 
289
- // Determine if RHS is a boxed variable (could be a struct or other variable)
290
- needsBoxedAccessRHS := false
282
+ // Determine if RHS is a variable referenced variable (could be a struct or other variable)
283
+ needsVarRefedAccessRHS := false
291
284
  var rhsObj types.Object
292
285
 
293
286
  // Check if RHS is an identifier (variable name)
@@ -298,17 +291,17 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
298
291
  rhsObj = c.pkg.TypesInfo.Defs[rhsIdent]
299
292
  }
300
293
 
301
- // Important: For struct copying, we need to check if the variable itself is boxed
302
- // Important: For struct copying, we need to check if the variable needs boxed access
303
- // This is more comprehensive than just checking if it's boxed
294
+ // Important: For struct copying, we need to check if the variable itself is variable referenced
295
+ // Important: For struct copying, we need to check if the variable needs variable referenced access
296
+ // This is more comprehensive than just checking if it's variable referenced
304
297
  if rhsObj != nil {
305
- needsBoxedAccessRHS = c.analysis.NeedsBoxedAccess(rhsObj)
298
+ needsVarRefedAccessRHS = c.analysis.NeedsVarRefAccess(rhsObj)
306
299
  }
307
300
  }
308
301
 
309
302
  // Handle different cases for struct cloning
310
303
  if shouldApplyClone(c.pkg, r) {
311
- // For other expressions, we need to handle boxed access differently
304
+ // For other expressions, we need to handle variable referenced access differently
312
305
  if _, isIdent := r.(*ast.Ident); isIdent {
313
306
  // For identifiers, WriteValueExpr already adds .value if needed
314
307
  if err := c.WriteValueExpr(r); err != nil {
@@ -319,14 +312,15 @@ func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Toke
319
312
  if err := c.WriteValueExpr(r); err != nil {
320
313
  return err
321
314
  }
322
- // Only add .value for non-identifiers that need boxed access
323
- if needsBoxedAccessRHS {
324
- c.tsw.WriteLiterally(".value") // Access the boxed value
315
+ // Only add .value for non-identifiers that need variable referenced access
316
+ if needsVarRefedAccessRHS {
317
+ c.tsw.WriteLiterally(".value") // Access the variable referenced value
325
318
  }
326
319
  }
327
320
 
328
321
  c.tsw.WriteLiterally(".clone()") // Always add clone for struct values
329
322
  } else {
323
+ // Non-struct case: write RHS normally
330
324
  if err := c.WriteValueExpr(r); err != nil { // RHS is a non-struct value
331
325
  return err
332
326
  }
@@ -0,0 +1,102 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "os"
6
+ "path/filepath"
7
+ "testing"
8
+
9
+ "github.com/sirupsen/logrus"
10
+ )
11
+
12
+ func TestEmitBuiltinOption(t *testing.T) {
13
+ // Create a temporary directory for the test output
14
+ tempDir, err := os.MkdirTemp("", "goscript-test-emit-builtin")
15
+ if err != nil {
16
+ t.Fatalf("Failed to create temp dir: %v", err)
17
+ }
18
+ defer os.RemoveAll(tempDir)
19
+
20
+ // Setup logger
21
+ log := logrus.New()
22
+ log.SetLevel(logrus.DebugLevel)
23
+ le := logrus.NewEntry(log)
24
+
25
+ // Case 1: DisableEmitBuiltin = true (default behavior in compliance tests)
26
+ t.Run("DisableEmitBuiltin=true", func(t *testing.T) {
27
+ outputDir := filepath.Join(tempDir, "disabled")
28
+ config := &Config{
29
+ OutputPath: outputDir,
30
+ AllDependencies: true,
31
+ DisableEmitBuiltin: true,
32
+ }
33
+
34
+ compiler, err := NewCompiler(config, le, nil)
35
+ if err != nil {
36
+ t.Fatalf("Failed to create compiler: %v", err)
37
+ }
38
+
39
+ // Compile a package that depends on builtin (time)
40
+ err = compiler.CompilePackages(context.Background(), "time")
41
+ if err != nil {
42
+ t.Fatalf("Compilation failed: %v", err)
43
+ }
44
+
45
+ // Check that the unsafe package wasn't emitted (we know it's handwritten)
46
+ unsafePath := filepath.Join(outputDir, "@goscript/unsafe")
47
+ if _, err := os.Stat(unsafePath); !os.IsNotExist(err) {
48
+ t.Errorf("unsafe package was emitted when DisableEmitBuiltin=true")
49
+ }
50
+
51
+ // Also check for runtime package
52
+ runtimePath := filepath.Join(outputDir, "@goscript/runtime")
53
+ if _, err := os.Stat(runtimePath); !os.IsNotExist(err) {
54
+ t.Errorf("runtime package was emitted when DisableEmitBuiltin=true")
55
+ }
56
+
57
+ // But time package should have been emitted
58
+ timePath := filepath.Join(outputDir, "@goscript/time")
59
+ if _, err := os.Stat(timePath); os.IsNotExist(err) {
60
+ t.Errorf("time package was not emitted")
61
+ }
62
+ })
63
+
64
+ // Case 2: DisableEmitBuiltin = false (new behavior for third-party projects)
65
+ t.Run("DisableEmitBuiltin=false", func(t *testing.T) {
66
+ outputDir := filepath.Join(tempDir, "enabled")
67
+ config := &Config{
68
+ OutputPath: outputDir,
69
+ AllDependencies: true,
70
+ DisableEmitBuiltin: false,
71
+ }
72
+
73
+ compiler, err := NewCompiler(config, le, nil)
74
+ if err != nil {
75
+ t.Fatalf("Failed to create compiler: %v", err)
76
+ }
77
+
78
+ // Compile a package that depends on builtin (time)
79
+ err = compiler.CompilePackages(context.Background(), "time")
80
+ if err != nil {
81
+ t.Fatalf("Compilation failed: %v", err)
82
+ }
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
+ // Time package should also have been emitted
97
+ timePath := filepath.Join(outputDir, "@goscript/time")
98
+ if _, err := os.Stat(timePath); os.IsNotExist(err) {
99
+ t.Errorf("time package was not emitted")
100
+ }
101
+ })
102
+ }
@@ -150,13 +150,39 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
150
150
  for _, pkg := range pkgs {
151
151
  // Check if the package has a handwritten equivalent
152
152
  if !slices.Contains(patternPkgPaths, pkg.PkgPath) {
153
- _, gsErr := gs.GsOverrides.ReadDir("gs/" + pkg.PkgPath)
153
+ gsSourcePath := "gs/" + pkg.PkgPath
154
+ _, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
154
155
  if gsErr != nil && !os.IsNotExist(gsErr) {
155
156
  return gsErr
156
157
  }
157
158
  if gsErr == nil {
158
- c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
159
- continue
159
+ if c.config.DisableEmitBuiltin {
160
+ c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
161
+ continue
162
+ } else {
163
+ // If DisableEmitBuiltin is false, we need to copy the handwritten package to the output directory
164
+ c.le.Infof("Copying handwritten package %s to output directory", pkg.PkgPath)
165
+
166
+ // Compute output path for this package
167
+ outputPath := ComputeModulePath(c.config.OutputPath, pkg.PkgPath)
168
+
169
+ // Remove existing directory if it exists
170
+ if err := os.RemoveAll(outputPath); err != nil {
171
+ return fmt.Errorf("failed to remove existing output directory for %s: %w", pkg.PkgPath, err)
172
+ }
173
+
174
+ // Create the output directory
175
+ if err := os.MkdirAll(outputPath, 0o755); err != nil {
176
+ return fmt.Errorf("failed to create output directory for %s: %w", pkg.PkgPath, err)
177
+ }
178
+
179
+ // Copy files from embedded FS to output directory
180
+ if err := c.copyEmbeddedPackage(gsSourcePath, outputPath); err != nil {
181
+ return fmt.Errorf("failed to copy embedded package %s: %w", pkg.PkgPath, err)
182
+ }
183
+
184
+ continue
185
+ }
160
186
  }
161
187
  }
162
188
 
@@ -202,7 +228,7 @@ func NewPackageCompiler(
202
228
  le: le,
203
229
  pkg: pkg,
204
230
  compilerConf: compilerConf,
205
- outputPath: ComputeModulePath(compilerConf.OutputPathRoot, pkg.PkgPath),
231
+ outputPath: ComputeModulePath(compilerConf.OutputPath, pkg.PkgPath),
206
232
  }
207
233
 
208
234
  return res, nil
@@ -287,7 +313,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
287
313
  // CompileFile handles the compilation of a single Go source file to TypeScript.
288
314
  // It first performs a pre-compilation analysis of the file using `AnalyzeFile`
289
315
  // to gather information necessary for accurate TypeScript generation (e.g.,
290
- // about boxing, async functions, defer statements).
316
+ // about varRefing, async functions, defer statements).
291
317
  // Then, it creates a `FileCompiler` instance for the file and invokes its
292
318
  // `Compile` method to generate the TypeScript code.
293
319
  func (p *PackageCompiler) CompileFile(ctx context.Context, name string, syntax *ast.File) error {
@@ -351,7 +377,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
351
377
  pkgPath := c.pkg.PkgPath
352
378
 
353
379
  outputFilePath := TranslateGoFilePathToTypescriptFilePath(pkgPath, filepath.Base(c.fullPath))
354
- outputFilePathAbs := filepath.Join(c.compilerConfig.OutputPathRoot, outputFilePath)
380
+ outputFilePathAbs := filepath.Join(c.compilerConfig.OutputPath, outputFilePath)
355
381
 
356
382
  if err := os.MkdirAll(filepath.Dir(outputFilePathAbs), 0o755); err != nil {
357
383
  return err
@@ -382,7 +408,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
382
408
  // GoToTSCompiler is the core component responsible for translating Go AST nodes
383
409
  // and type information into TypeScript code. It uses a `TSCodeWriter` to output
384
410
  // the generated TypeScript and relies on `Analysis` data to make informed
385
- // decisions about code generation (e.g., boxing, async behavior).
411
+ // decisions about code generation (e.g., varRefing, async behavior).
386
412
  type GoToTSCompiler struct {
387
413
  tsw *TSCodeWriter
388
414
 
@@ -408,14 +434,12 @@ func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, analysis *Analy
408
434
  // variable, function name) into its TypeScript equivalent.
409
435
  // - If the identifier is `nil`, it writes `null`.
410
436
  // - Otherwise, it writes the identifier's name.
411
- // - If `accessBoxedValue` is true and the analysis (`c.analysis.NeedsBoxedAccess`)
412
- // indicates that this identifier refers to a variable whose value is stored
413
- // in a box (due to its address being taken or other boxing requirements),
414
- // it appends `.value` to access the actual value from the box.
437
+ // - If `accessVarRefedValue` is true and the analysis (`c.analysis.NeedsVarRefAccess`)
438
+ // indicates the variable is variable referenced, `.value` is appended to access the contained value.
415
439
  //
416
440
  // This function relies on `go/types` (`TypesInfo.Uses` or `Defs`) to resolve
417
- // the identifier and the `Analysis` data to determine boxing needs.
418
- func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessBoxedValue bool) {
441
+ // the identifier and the `Analysis` data to determine varRefing needs.
442
+ func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessVarRefedValue bool) {
419
443
  if exp.Name == "nil" {
420
444
  c.tsw.WriteLiterally("null")
421
445
  return
@@ -432,7 +456,7 @@ func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessBoxedValue bool) {
432
456
  c.tsw.WriteLiterally(exp.Name)
433
457
 
434
458
  // Determine if we need to access .value based on analysis data
435
- if obj != nil && accessBoxedValue && c.analysis.NeedsBoxedAccess(obj) {
459
+ if obj != nil && accessVarRefedValue && c.analysis.NeedsVarRefAccess(obj) {
436
460
  c.tsw.WriteLiterally("!.value")
437
461
  }
438
462
  }
@@ -627,3 +651,44 @@ func (c *GoToTSCompiler) WriteDoc(doc *ast.CommentGroup) {
627
651
  }
628
652
  }
629
653
  }
654
+
655
+ // copyEmbeddedPackage recursively copies files from an embedded FS path to a filesystem directory.
656
+ // It handles both regular files and directories.
657
+ func (c *Compiler) copyEmbeddedPackage(embeddedPath string, outputPath string) error {
658
+ // List the entries in the embedded path
659
+ entries, err := gs.GsOverrides.ReadDir(embeddedPath)
660
+ if err != nil {
661
+ return fmt.Errorf("failed to read embedded directory %s: %w", embeddedPath, err)
662
+ }
663
+
664
+ // Process each entry
665
+ for _, entry := range entries {
666
+ entryPath := filepath.Join(embeddedPath, entry.Name())
667
+ outputEntryPath := filepath.Join(outputPath, entry.Name())
668
+
669
+ if entry.IsDir() {
670
+ // Create the output directory
671
+ if err := os.MkdirAll(outputEntryPath, 0o755); err != nil {
672
+ return fmt.Errorf("failed to create output directory %s: %w", outputEntryPath, err)
673
+ }
674
+
675
+ // Recursively copy the directory contents
676
+ if err := c.copyEmbeddedPackage(entryPath, outputEntryPath); err != nil {
677
+ return err
678
+ }
679
+ } else {
680
+ // Read the file content from the embedded FS
681
+ content, err := gs.GsOverrides.ReadFile(entryPath)
682
+ if err != nil {
683
+ return fmt.Errorf("failed to read embedded file %s: %w", entryPath, err)
684
+ }
685
+
686
+ // Write the content to the output file
687
+ if err := os.WriteFile(outputEntryPath, content, 0o644); err != nil {
688
+ return fmt.Errorf("failed to write file %s: %w", outputEntryPath, err)
689
+ }
690
+ }
691
+ }
692
+
693
+ return nil
694
+ }