goscript 0.0.84 → 0.1.0
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 +13 -1
- package/cmd/goscript/cmd_compile.go +70 -69
- package/cmd/goscript/cmd_compile_test.go +79 -0
- package/cmd/goscript/main.go +10 -5
- package/compiler/compile-request.go +218 -0
- package/compiler/compiler.go +16 -1336
- package/compiler/compliance_test.go +196 -0
- package/compiler/config.go +6 -13
- package/compiler/diagnostic.go +70 -0
- package/compiler/index.test.ts +28 -28
- package/compiler/index.ts +40 -72
- package/compiler/lowered-program.go +132 -0
- package/compiler/lowering.go +3576 -0
- package/compiler/override-registry.go +422 -0
- package/compiler/override-registry_test.go +207 -0
- package/compiler/package-graph.go +231 -0
- package/compiler/package-graph_test.go +281 -0
- package/compiler/result.go +13 -0
- package/compiler/runtime-contract.go +279 -0
- package/compiler/runtime-contract_test.go +90 -0
- package/compiler/semantic-model-types.go +110 -0
- package/compiler/semantic-model.go +922 -0
- package/compiler/semantic-model_test.go +416 -0
- package/compiler/service.go +133 -0
- package/compiler/skeleton_test.go +1145 -0
- package/compiler/typescript-emitter.go +663 -0
- package/compiler/wasm/compile.go +2 -3
- package/compiler/wasm/compile_test.go +29 -0
- package/compiler/wasm_api.go +10 -159
- package/dist/compiler/index.d.ts +1 -3
- package/dist/compiler/index.js +31 -55
- package/dist/compiler/index.js.map +1 -1
- package/dist/gs/builtin/builtin.d.ts +13 -0
- package/dist/gs/builtin/builtin.js +23 -0
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/channel.d.ts +3 -3
- package/dist/gs/builtin/channel.js.map +1 -1
- package/dist/gs/builtin/hostio.d.ts +15 -1
- package/dist/gs/builtin/hostio.js +134 -49
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/index.d.ts +1 -0
- package/dist/gs/builtin/index.js +1 -0
- package/dist/gs/builtin/index.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +11 -0
- package/dist/gs/builtin/type.js +55 -1
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/bytes/buffer.gs.js.map +1 -1
- package/dist/gs/bytes/bytes.gs.js.map +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/crypto/rand/index.d.ts +5 -0
- package/dist/gs/crypto/rand/index.js +77 -0
- package/dist/gs/crypto/rand/index.js.map +1 -0
- package/dist/gs/encoding/json/index.d.ts +3 -0
- package/dist/gs/encoding/json/index.js +160 -0
- package/dist/gs/encoding/json/index.js.map +1 -0
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
- package/dist/gs/go/scanner/index.d.ts +29 -0
- package/dist/gs/go/scanner/index.js +120 -0
- package/dist/gs/go/scanner/index.js.map +1 -0
- package/dist/gs/go/token/index.d.ts +31 -0
- package/dist/gs/go/token/index.js +82 -0
- package/dist/gs/go/token/index.js.map +1 -0
- package/dist/gs/internal/abi/index.js.map +1 -1
- package/dist/gs/io/fs/fs.js.map +1 -1
- package/dist/gs/io/fs/readdir.js.map +1 -1
- package/dist/gs/io/fs/readfile.js.map +1 -1
- package/dist/gs/io/fs/stat.js.map +1 -1
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/io.js.map +1 -1
- package/dist/gs/os/dir_unix.gs.js.map +1 -1
- package/dist/gs/os/error.gs.js +2 -4
- package/dist/gs/os/error.gs.js.map +1 -1
- package/dist/gs/os/exec.gs.js.map +1 -1
- package/dist/gs/os/exec_posix.gs.js.map +1 -1
- package/dist/gs/os/rawconn_js.gs.js.map +1 -1
- package/dist/gs/os/root_js.gs.js.map +1 -1
- package/dist/gs/os/tempfile.gs.js +66 -9
- package/dist/gs/os/tempfile.gs.js.map +1 -1
- package/dist/gs/os/types.gs.js.map +1 -1
- package/dist/gs/os/types_js.gs.js +9 -9
- package/dist/gs/os/types_js.gs.js.map +1 -1
- package/dist/gs/os/types_unix.gs.js.map +1 -1
- package/dist/gs/path/filepath/match.js.map +1 -1
- package/dist/gs/path/match.js.map +1 -1
- package/dist/gs/path/path.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +2 -2
- package/dist/gs/reflect/index.js +1 -1
- package/dist/gs/reflect/index.js.map +1 -1
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +2 -1
- package/dist/gs/reflect/type.js +85 -14
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/visiblefields.js.map +1 -1
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/sort/sort.gs.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/strconv/quote.gs.js.map +1 -1
- package/dist/gs/strings/builder.js.map +1 -1
- package/dist/gs/strings/reader.js.map +1 -1
- package/dist/gs/strings/replace.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/sync/atomic/value.gs.js.map +1 -1
- package/dist/gs/sync/sync.d.ts +1 -0
- package/dist/gs/sync/sync.js +12 -0
- package/dist/gs/sync/sync.js.map +1 -1
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unicode/unicode.js.map +1 -1
- package/go.mod +2 -2
- package/gs/builtin/builtin.ts +27 -0
- package/gs/builtin/hostio.test.ts +177 -0
- package/gs/builtin/hostio.ts +171 -56
- package/gs/builtin/index.ts +1 -0
- package/gs/builtin/runtime-contract.test.ts +230 -0
- package/gs/builtin/type.ts +84 -1
- package/gs/crypto/rand/index.test.ts +32 -0
- package/gs/crypto/rand/index.ts +90 -0
- package/gs/crypto/rand/meta.json +5 -0
- package/gs/encoding/json/index.test.ts +65 -0
- package/gs/encoding/json/index.ts +186 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
- package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
- package/gs/go/scanner/index.test.ts +50 -0
- package/gs/go/scanner/index.ts +157 -0
- package/gs/go/token/index.test.ts +21 -0
- package/gs/go/token/index.ts +120 -0
- package/gs/os/file_unix_js.test.ts +50 -0
- package/gs/os/meta.json +1 -2
- package/gs/os/tempfile.gs.test.ts +85 -0
- package/gs/os/tempfile.gs.ts +71 -11
- package/gs/os/types_js.gs.ts +9 -9
- package/gs/reflect/index.ts +1 -1
- package/gs/reflect/type.ts +106 -17
- package/gs/reflect/typefor.test.ts +75 -0
- package/gs/sync/sync.test.ts +24 -0
- package/gs/sync/sync.ts +12 -0
- package/package.json +13 -13
- package/compiler/analysis.go +0 -3475
- package/compiler/analysis_test.go +0 -338
- package/compiler/assignment.go +0 -580
- package/compiler/builtin_test.go +0 -92
- package/compiler/code-writer.go +0 -115
- package/compiler/compiler_test.go +0 -149
- package/compiler/composite-lit.go +0 -779
- package/compiler/config_test.go +0 -62
- package/compiler/constraint.go +0 -86
- package/compiler/decl.go +0 -801
- package/compiler/expr-call-async.go +0 -188
- package/compiler/expr-call-builtins.go +0 -208
- package/compiler/expr-call-helpers.go +0 -382
- package/compiler/expr-call-make.go +0 -318
- package/compiler/expr-call-type-conversion.go +0 -520
- package/compiler/expr-call.go +0 -413
- package/compiler/expr-selector.go +0 -343
- package/compiler/expr-star.go +0 -82
- package/compiler/expr-type.go +0 -442
- package/compiler/expr-value.go +0 -89
- package/compiler/expr.go +0 -773
- package/compiler/field.go +0 -183
- package/compiler/gs_dependencies_test.go +0 -298
- package/compiler/lit.go +0 -322
- package/compiler/output.go +0 -72
- package/compiler/primitive.go +0 -149
- package/compiler/protobuf.go +0 -697
- package/compiler/sanitize.go +0 -100
- package/compiler/spec-struct.go +0 -995
- package/compiler/spec-value.go +0 -540
- package/compiler/spec.go +0 -725
- package/compiler/stmt-assign.go +0 -664
- package/compiler/stmt-for.go +0 -266
- package/compiler/stmt-range.go +0 -475
- package/compiler/stmt-select.go +0 -262
- package/compiler/stmt-type-switch.go +0 -147
- package/compiler/stmt.go +0 -1308
- package/compiler/type-assert.go +0 -386
- package/compiler/type-info.go +0 -156
- package/compiler/type-utils.go +0 -207
- package/compiler/type.go +0 -892
package/compiler/compiler.go
CHANGED
|
@@ -2,1360 +2,40 @@ package compiler
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
-
"crypto/sha256"
|
|
6
|
-
"encoding/hex"
|
|
7
|
-
"encoding/json"
|
|
8
|
-
"fmt"
|
|
9
|
-
"go/ast"
|
|
10
|
-
"go/constant"
|
|
11
|
-
"go/token"
|
|
12
|
-
"go/types"
|
|
13
|
-
"os"
|
|
14
|
-
"path/filepath"
|
|
15
|
-
"slices"
|
|
16
|
-
"strings"
|
|
17
5
|
|
|
18
|
-
gs "github.com/aperturerobotics/goscript"
|
|
19
6
|
"github.com/sirupsen/logrus"
|
|
20
|
-
"golang.org/x/mod/modfile"
|
|
21
7
|
"golang.org/x/tools/go/packages"
|
|
22
8
|
)
|
|
23
9
|
|
|
24
|
-
// Compiler is the
|
|
25
|
-
// and compilation of Go packages into TypeScript. It holds project-wide
|
|
26
|
-
// configuration and uses `golang.org/x/tools/go/packages` to load
|
|
27
|
-
// Go package information.
|
|
10
|
+
// Compiler is the public Go adapter for the v2 compile service.
|
|
28
11
|
type Compiler struct {
|
|
29
|
-
le
|
|
30
|
-
config
|
|
31
|
-
|
|
12
|
+
le *logrus.Entry
|
|
13
|
+
config Config
|
|
14
|
+
service *CompileService
|
|
32
15
|
}
|
|
33
16
|
|
|
34
|
-
// NewCompiler builds a
|
|
35
|
-
|
|
36
|
-
// `packages.Config` for loading Go packages. If `opts` is nil,
|
|
37
|
-
// default options are used, configured for JavaScript/WebAssembly (js/wasm)
|
|
38
|
-
// target and to load comprehensive package information (types, syntax, etc.).
|
|
39
|
-
// It validates the provided configuration before creating the compiler.
|
|
40
|
-
func NewCompiler(conf *Config, le *logrus.Entry, opts *packages.Config) (*Compiler, error) {
|
|
17
|
+
// NewCompiler builds a compiler adapter over the v2 compile service.
|
|
18
|
+
func NewCompiler(conf *Config, le *logrus.Entry, _ *packages.Config) (*Compiler, error) {
|
|
41
19
|
if err := conf.Validate(); err != nil {
|
|
42
20
|
return nil, err
|
|
43
21
|
}
|
|
44
22
|
|
|
45
|
-
if opts == nil {
|
|
46
|
-
opts = &packages.Config{Env: os.Environ()}
|
|
47
|
-
}
|
|
48
|
-
// opts.Logf = c.le.Debugf
|
|
49
|
-
opts.Tests = false
|
|
50
|
-
opts.Env = append(opts.Env, "GOOS=js", "GOARCH=wasm")
|
|
51
|
-
opts.Dir = conf.Dir
|
|
52
|
-
opts.BuildFlags = conf.BuildFlags
|
|
53
|
-
|
|
54
|
-
// NeedName adds Name and PkgPath.
|
|
55
|
-
// NeedFiles adds GoFiles and OtherFiles.
|
|
56
|
-
// NeedCompiledGoFiles adds CompiledGoFiles.
|
|
57
|
-
// NeedImports adds Imports. If NeedDeps is not set, the Imports field will contain
|
|
58
|
-
// "placeholder" Packages with only the ID set.
|
|
59
|
-
// NeedDeps adds the fields requested by the LoadMode in the packages in Imports.
|
|
60
|
-
// NeedExportsFile adds ExportsFile.
|
|
61
|
-
// NeedTypes adds Types, Fset, and IllTyped.
|
|
62
|
-
// NeedSyntax adds Syntax.
|
|
63
|
-
// NeedTypesInfo adds TypesInfo.
|
|
64
|
-
// NeedTypesSizes adds TypesSizes.
|
|
65
|
-
// TODO: disable these if not needed
|
|
66
|
-
opts.Mode |= packages.NeedName |
|
|
67
|
-
packages.NeedFiles |
|
|
68
|
-
packages.NeedCompiledGoFiles |
|
|
69
|
-
packages.NeedImports |
|
|
70
|
-
packages.NeedDeps |
|
|
71
|
-
packages.NeedExportFile |
|
|
72
|
-
packages.NeedTypes |
|
|
73
|
-
packages.NeedSyntax |
|
|
74
|
-
packages.NeedTypesInfo |
|
|
75
|
-
packages.NeedTypesSizes
|
|
76
|
-
|
|
77
23
|
return &Compiler{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
24
|
+
le: le,
|
|
25
|
+
config: *conf,
|
|
26
|
+
service: NewCompileService(),
|
|
81
27
|
}, nil
|
|
82
28
|
}
|
|
83
29
|
|
|
84
|
-
//
|
|
85
|
-
type CompilationResult struct {
|
|
86
|
-
// CompiledPackages contains the package paths of all packages that were actually compiled to TypeScript
|
|
87
|
-
CompiledPackages []string
|
|
88
|
-
// CopiedPackages contains the package paths of all packages that were copied from handwritten sources
|
|
89
|
-
CopiedPackages []string
|
|
90
|
-
// OriginalPackages contains the package paths that were explicitly requested for compilation
|
|
91
|
-
OriginalPackages []string
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// resolveReplaceDirectives transforms local path patterns (like ./subpkg) to their
|
|
95
|
-
// corresponding module paths using replace directives from go.mod.
|
|
96
|
-
// This allows users to specify local paths that are mapped via replace directives.
|
|
97
|
-
// For example, if go.mod has:
|
|
98
|
-
//
|
|
99
|
-
// replace github.com/example/lib => ./local/lib
|
|
100
|
-
//
|
|
101
|
-
// And the user runs: goscript compile ./local/lib
|
|
102
|
-
// This function transforms ./local/lib to github.com/example/lib
|
|
103
|
-
func (c *Compiler) resolveReplaceDirectives(patterns []string) ([]string, error) {
|
|
104
|
-
dir := c.config.Dir
|
|
105
|
-
if dir == "" {
|
|
106
|
-
dir = "."
|
|
107
|
-
}
|
|
108
|
-
goModPath := filepath.Join(dir, "go.mod")
|
|
109
|
-
|
|
110
|
-
data, err := os.ReadFile(goModPath)
|
|
111
|
-
if err != nil {
|
|
112
|
-
if os.IsNotExist(err) {
|
|
113
|
-
return patterns, nil
|
|
114
|
-
}
|
|
115
|
-
return nil, err
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
modFile, err := modfile.Parse(goModPath, data, nil)
|
|
119
|
-
if err != nil {
|
|
120
|
-
return nil, err
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Build replace map: local path -> module path
|
|
124
|
-
// The replace directive syntax is: replace old => new
|
|
125
|
-
// For local replacements: replace github.com/example/lib => ./local/lib
|
|
126
|
-
// So r.Old.Path = "github.com/example/lib" (the module path)
|
|
127
|
-
// And r.New.Path = "./local/lib" (the local replacement path)
|
|
128
|
-
replaceMap := make(map[string]string)
|
|
129
|
-
for _, r := range modFile.Replace {
|
|
130
|
-
if r.New.Version == "" { // Local path replacements have no version
|
|
131
|
-
// Normalize the path for consistent comparison across platforms
|
|
132
|
-
localPath := filepath.Clean(r.New.Path)
|
|
133
|
-
replaceMap[localPath] = r.Old.Path
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Transform patterns that are local paths to their module path equivalents
|
|
138
|
-
result := make([]string, len(patterns))
|
|
139
|
-
for i, pattern := range patterns {
|
|
140
|
-
// Check if this is a filesystem path (relative or absolute)
|
|
141
|
-
if filepath.IsAbs(pattern) || strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") {
|
|
142
|
-
// Normalize the pattern for consistent comparison
|
|
143
|
-
normalizedPattern := filepath.Clean(pattern)
|
|
144
|
-
if modulePath, ok := replaceMap[normalizedPattern]; ok {
|
|
145
|
-
result[i] = modulePath
|
|
146
|
-
continue
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
result[i] = pattern
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return result, nil
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// CompilePackages loads Go packages based on the provided patterns and
|
|
156
|
-
// then compiles each loaded package into TypeScript. It uses the context for
|
|
157
|
-
// cancellation and applies the compiler's configured options during package loading.
|
|
158
|
-
// For each successfully loaded package, it creates a `PackageCompiler` and
|
|
159
|
-
// invokes its `Compile` method.
|
|
160
|
-
// If c.config.AllDependencies is true, it will also compile all dependencies
|
|
161
|
-
// of the requested packages, including standard library dependencies.
|
|
162
|
-
// Returns a CompilationResult with information about what was compiled.
|
|
30
|
+
// CompilePackages compiles Go package patterns through the v2 pipeline.
|
|
163
31
|
func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*CompilationResult, error) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// Resolve local path patterns using replace directives from go.mod
|
|
168
|
-
resolvedPatterns, err := c.resolveReplaceDirectives(patterns)
|
|
169
|
-
if err != nil {
|
|
170
|
-
return nil, fmt.Errorf("failed to resolve replace directives: %w", err)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// First, load the initial packages with NeedImports to get all dependencies
|
|
174
|
-
opts.Mode |= packages.NeedImports
|
|
175
|
-
pkgs, err := packages.Load(&opts, resolvedPatterns...)
|
|
176
|
-
if err != nil {
|
|
177
|
-
return nil, fmt.Errorf("failed to load packages: %w", err)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// build a list of packages that patterns matched
|
|
181
|
-
patternPkgPaths := make([]string, 0, len(pkgs))
|
|
182
|
-
for _, pkg := range pkgs {
|
|
183
|
-
patternPkgPaths = append(patternPkgPaths, pkg.PkgPath)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
result := &CompilationResult{
|
|
187
|
-
OriginalPackages: patternPkgPaths,
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// If AllDependencies is true, we need to collect all dependencies
|
|
191
|
-
if c.config.AllDependencies {
|
|
192
|
-
// Create a set to track processed packages by their ID
|
|
193
|
-
processed := make(map[string]bool)
|
|
194
|
-
var allPkgs []*packages.Package
|
|
195
|
-
|
|
196
|
-
// Helper function to check if a package has a handwritten equivalent
|
|
197
|
-
hasHandwrittenEquivalent := func(pkgPath string) bool {
|
|
198
|
-
gsSourcePath := "gs/" + pkgPath
|
|
199
|
-
_, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
|
|
200
|
-
return gsErr == nil
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Visit all packages and their dependencies
|
|
204
|
-
var visit func(pkg *packages.Package)
|
|
205
|
-
visit = func(pkg *packages.Package) {
|
|
206
|
-
if pkg == nil || processed[pkg.ID] {
|
|
207
|
-
return
|
|
208
|
-
}
|
|
209
|
-
processed[pkg.ID] = true
|
|
210
|
-
|
|
211
|
-
// Add this package to the list of all packages
|
|
212
|
-
allPkgs = append(allPkgs, pkg)
|
|
213
|
-
|
|
214
|
-
// Check if this package has a handwritten equivalent
|
|
215
|
-
if hasHandwrittenEquivalent(pkg.PkgPath) {
|
|
216
|
-
// Add this package but don't visit its dependencies
|
|
217
|
-
return
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Visit all imports, including standard library packages
|
|
221
|
-
for _, imp := range pkg.Imports {
|
|
222
|
-
// Skip protobuf-go-lite packages and their dependencies
|
|
223
|
-
if isProtobufGoLitePackage(imp.PkgPath) {
|
|
224
|
-
continue
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Skip packages that are only used by .pb.go files
|
|
228
|
-
if isPackageOnlyUsedByProtobufFiles(pkg, imp.PkgPath) {
|
|
229
|
-
continue
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
visit(imp)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Start visiting from the initial packages
|
|
237
|
-
for _, pkg := range pkgs {
|
|
238
|
-
visit(pkg)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Now we have collected all dependencies, but they only have minimal information.
|
|
242
|
-
// We need to reload them with complete type information for compilation.
|
|
243
|
-
// Collect all package paths from the dependency graph
|
|
244
|
-
var pkgPaths []string
|
|
245
|
-
pkgPathSet := make(map[string]bool) // Use set to avoid duplicates
|
|
246
|
-
|
|
247
|
-
for _, pkg := range allPkgs {
|
|
248
|
-
if pkg.PkgPath != "" && !pkgPathSet[pkg.PkgPath] {
|
|
249
|
-
pkgPaths = append(pkgPaths, pkg.PkgPath)
|
|
250
|
-
pkgPathSet[pkg.PkgPath] = true
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Reload all collected packages with complete type information
|
|
255
|
-
if len(pkgPaths) > 0 {
|
|
256
|
-
fullOpts := c.opts
|
|
257
|
-
fullOpts.Context = ctx
|
|
258
|
-
// Use LoadAllSyntax to get complete type information, syntax trees, and type checking
|
|
259
|
-
fullOpts.Mode = packages.LoadAllSyntax
|
|
260
|
-
|
|
261
|
-
reloadedPkgs, err := packages.Load(&fullOpts, pkgPaths...)
|
|
262
|
-
if err != nil {
|
|
263
|
-
return nil, fmt.Errorf("failed to reload packages with complete type information: %w", err)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Replace the minimal packages with the fully loaded ones
|
|
267
|
-
pkgs = reloadedPkgs
|
|
268
|
-
} else {
|
|
269
|
-
// No packages to reload, use the original set
|
|
270
|
-
pkgs = allPkgs
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// If DisableEmitBuiltin is false, we need to copy the builtin package to the output directory
|
|
275
|
-
if !c.config.DisableEmitBuiltin {
|
|
276
|
-
c.le.Debugf("Copying builtin package to output directory")
|
|
277
|
-
builtinPath := "gs/builtin"
|
|
278
|
-
outputPath := ComputeModulePath(c.config.OutputPath, "builtin")
|
|
279
|
-
if err := c.copyEmbeddedPackage(builtinPath, outputPath); err != nil {
|
|
280
|
-
return nil, fmt.Errorf("failed to copy builtin package to output directory: %w", err)
|
|
281
|
-
}
|
|
282
|
-
result.CopiedPackages = append(result.CopiedPackages, "builtin")
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Track which gs packages have been processed to avoid duplicates
|
|
286
|
-
processedGsPackages := make(map[string]bool)
|
|
287
|
-
|
|
288
|
-
// Create a map of all loaded packages for dependency analysis
|
|
289
|
-
allPackages := make(map[string]*packages.Package)
|
|
290
|
-
for _, pkg := range pkgs {
|
|
291
|
-
allPackages[pkg.PkgPath] = pkg
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Compile all packages
|
|
295
|
-
for _, pkg := range pkgs {
|
|
296
|
-
// Check if the package has a handwritten equivalent
|
|
297
|
-
// If the package was explicitly requested, skip this logic
|
|
298
|
-
if !slices.Contains(patternPkgPaths, pkg.PkgPath) {
|
|
299
|
-
gsSourcePath := "gs/" + pkg.PkgPath
|
|
300
|
-
_, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
|
|
301
|
-
if gsErr != nil && !os.IsNotExist(gsErr) {
|
|
302
|
-
return nil, gsErr
|
|
303
|
-
}
|
|
304
|
-
if gsErr == nil {
|
|
305
|
-
if c.config.DisableEmitBuiltin {
|
|
306
|
-
// c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
|
|
307
|
-
result.CopiedPackages = append(result.CopiedPackages, pkg.PkgPath)
|
|
308
|
-
continue
|
|
309
|
-
} else {
|
|
310
|
-
// If DisableEmitBuiltin is false, we need to copy the handwritten package and its dependencies
|
|
311
|
-
if err := c.copyGsPackageWithDependencies(pkg.PkgPath, processedGsPackages, result); err != nil {
|
|
312
|
-
return nil, fmt.Errorf("failed to copy handwritten package %s with dependencies: %w", pkg.PkgPath, err)
|
|
313
|
-
}
|
|
314
|
-
continue
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Fail fast on packages that failed to load to avoid hiding errors
|
|
320
|
-
if len(pkg.Errors) > 0 {
|
|
321
|
-
var msgs []string
|
|
322
|
-
for _, e := range pkg.Errors {
|
|
323
|
-
// packages.Error is a struct; collect all messages
|
|
324
|
-
msgs = append(msgs, e.Error())
|
|
325
|
-
}
|
|
326
|
-
return nil, fmt.Errorf("package %s has load errors: %s", pkg.PkgPath, strings.Join(msgs, "; "))
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
pkgCompiler, err := NewPackageCompiler(c.le, &c.config, pkg, allPackages)
|
|
330
|
-
if err != nil {
|
|
331
|
-
return nil, fmt.Errorf("failed to create package compiler for %s: %w", pkg.PkgPath, err)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if err := pkgCompiler.Compile(ctx); err != nil {
|
|
335
|
-
return nil, fmt.Errorf("failed to compile package %s: %w", pkg.PkgPath, err)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
c.le.Info(pkg.PkgPath)
|
|
339
|
-
|
|
340
|
-
result.CompiledPackages = append(result.CompiledPackages, pkg.PkgPath)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return result, nil
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// PackageCompiler is responsible for compiling an entire Go package into
|
|
347
|
-
// its TypeScript equivalent. It manages the compilation of individual files
|
|
348
|
-
// within the package and determines the output path for the compiled package.
|
|
349
|
-
type PackageCompiler struct {
|
|
350
|
-
le *logrus.Entry
|
|
351
|
-
compilerConf *Config
|
|
352
|
-
outputPath string
|
|
353
|
-
pkg *packages.Package
|
|
354
|
-
allPackages map[string]*packages.Package
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// NewPackageCompiler creates a new `PackageCompiler` for a given Go package.
|
|
358
|
-
// It initializes the compiler with the necessary configuration, logger, and
|
|
359
|
-
// the `packages.Package` data obtained from `golang.org/x/tools/go/packages`.
|
|
360
|
-
// It also computes the base output path for the compiled TypeScript files of the package.
|
|
361
|
-
func NewPackageCompiler(
|
|
362
|
-
le *logrus.Entry,
|
|
363
|
-
compilerConf *Config,
|
|
364
|
-
pkg *packages.Package,
|
|
365
|
-
allPackages map[string]*packages.Package,
|
|
366
|
-
) (*PackageCompiler, error) {
|
|
367
|
-
res := &PackageCompiler{
|
|
368
|
-
le: le,
|
|
369
|
-
pkg: pkg,
|
|
370
|
-
compilerConf: compilerConf,
|
|
371
|
-
outputPath: ComputeModulePath(compilerConf.OutputPath, pkg.PkgPath),
|
|
372
|
-
allPackages: allPackages,
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return res, nil
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Compile orchestrates the compilation of all Go files within the package.
|
|
379
|
-
//
|
|
380
|
-
// It iterates through each syntax file (`ast.File`) of the package,
|
|
381
|
-
// determines its relative path for logging, and then invokes `CompileFile`
|
|
382
|
-
// to handle the compilation of that specific file.
|
|
383
|
-
//
|
|
384
|
-
// After compiling all files, it generates an index.ts file that re-exports
|
|
385
|
-
// all exported symbols, allowing the package to be imported correctly.
|
|
386
|
-
//
|
|
387
|
-
// The working directory (`wd`) is used to make file paths in logs more readable.
|
|
388
|
-
func (c *PackageCompiler) Compile(ctx context.Context) error {
|
|
389
|
-
wd := c.compilerConf.Dir
|
|
390
|
-
if wd == "" {
|
|
391
|
-
var err error
|
|
392
|
-
wd, err = os.Getwd()
|
|
393
|
-
if err != nil {
|
|
394
|
-
return err
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Perform package-level analysis for auto-imports
|
|
399
|
-
packageAnalysis := AnalyzePackageImports(c.pkg)
|
|
400
|
-
|
|
401
|
-
// Perform comprehensive package-level analysis for code generation
|
|
402
|
-
analysis := AnalyzePackageFiles(c.pkg, c.allPackages)
|
|
403
|
-
|
|
404
|
-
// Track all compiled files for later generating the index.ts
|
|
405
|
-
compiledFiles := make([]string, 0, len(c.pkg.CompiledGoFiles))
|
|
406
|
-
|
|
407
|
-
// Compile the files in the package one at a time
|
|
408
|
-
for i, f := range c.pkg.Syntax {
|
|
409
|
-
fileName := c.pkg.CompiledGoFiles[i]
|
|
410
|
-
relWdFileName, err := filepath.Rel(wd, fileName)
|
|
411
|
-
if err != nil {
|
|
412
|
-
return err
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Check if this is a .pb.go file that should be skipped
|
|
416
|
-
baseFileName := filepath.Base(fileName)
|
|
417
|
-
if before, ok := strings.CutSuffix(baseFileName, ".pb.go"); ok {
|
|
418
|
-
// Check if there's a corresponding .pb.ts file
|
|
419
|
-
pbTsFileName := before + ".pb.ts"
|
|
420
|
-
packageDir := filepath.Dir(fileName)
|
|
421
|
-
pbTsPath := filepath.Join(packageDir, pbTsFileName)
|
|
422
|
-
|
|
423
|
-
if _, err := os.Stat(pbTsPath); err == nil {
|
|
424
|
-
// .pb.ts file exists, copy it instead of transpiling .pb.go
|
|
425
|
-
c.le.WithField("file", relWdFileName).Debug("found .pb.ts file, copying instead of transpiling .pb.go")
|
|
426
|
-
|
|
427
|
-
if err := c.copyProtobufTSFile(pbTsPath, pbTsFileName); err != nil {
|
|
428
|
-
return fmt.Errorf("failed to copy protobuf .pb.ts file: %w", err)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Add the .pb file to our compiled files list for index generation
|
|
432
|
-
pbFileName := strings.TrimSuffix(baseFileName, ".pb.go") + ".pb"
|
|
433
|
-
compiledFiles = append(compiledFiles, pbFileName)
|
|
434
|
-
continue // Skip transpiling this .pb.go file
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// log just the filename
|
|
439
|
-
c.le.Debugf("GS: %s", filepath.Base(fileName))
|
|
440
|
-
if err := c.CompileFile(ctx, fileName, f, analysis, packageAnalysis); err != nil {
|
|
441
|
-
return err
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Add the base filename to our list for the index.ts generation
|
|
445
|
-
// Strip .go extension and add .gs
|
|
446
|
-
gsFileName := strings.TrimSuffix(baseFileName, ".go") + ".gs"
|
|
447
|
-
compiledFiles = append(compiledFiles, gsFileName)
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// After compiling all files, generate the index.ts file
|
|
451
|
-
if err := c.generateIndexFile(compiledFiles); err != nil {
|
|
452
|
-
return err
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return nil
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// generateIndexFile creates an index.ts file in the package output directory
|
|
459
|
-
// that re-exports only Go-exported symbols from the compiled TypeScript files.
|
|
460
|
-
// This ensures the package can be imported correctly by TypeScript modules
|
|
461
|
-
// while maintaining proper Go package boundaries.
|
|
462
|
-
func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
463
|
-
indexFilePath := filepath.Join(c.outputPath, "index.ts")
|
|
464
|
-
|
|
465
|
-
// Open the file for writing
|
|
466
|
-
indexFile, err := os.OpenFile(indexFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
|
|
467
|
-
if err != nil {
|
|
468
|
-
return err
|
|
469
|
-
}
|
|
470
|
-
defer indexFile.Close() //nolint:errcheck
|
|
471
|
-
|
|
472
|
-
// Write selective re-exports for each compiled file
|
|
473
|
-
for _, fileName := range compiledFiles {
|
|
474
|
-
// Check if this is a protobuf file
|
|
475
|
-
if strings.HasSuffix(fileName, ".pb") {
|
|
476
|
-
// For protobuf files, add a simple re-export
|
|
477
|
-
if err := c.writeProtobufExports(indexFile, fileName); err != nil {
|
|
478
|
-
return err
|
|
479
|
-
}
|
|
480
|
-
continue
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Find which symbols this file exports
|
|
484
|
-
var valueSymbols []string
|
|
485
|
-
var typeSymbols []string
|
|
486
|
-
var structSymbols []string // New: Track structs separately
|
|
487
|
-
|
|
488
|
-
// Find the corresponding syntax file
|
|
489
|
-
for i, syntax := range c.pkg.Syntax {
|
|
490
|
-
syntaxFileName := c.pkg.CompiledGoFiles[i]
|
|
491
|
-
syntaxBaseFileName := filepath.Base(syntaxFileName)
|
|
492
|
-
syntaxGsFileName := strings.TrimSuffix(syntaxBaseFileName, ".go") + ".gs"
|
|
493
|
-
|
|
494
|
-
if syntaxGsFileName != fileName {
|
|
495
|
-
continue
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Collect exported symbols from this specific file
|
|
499
|
-
for _, decl := range syntax.Decls {
|
|
500
|
-
switch d := decl.(type) {
|
|
501
|
-
case *ast.FuncDecl:
|
|
502
|
-
if d.Recv == nil && d.Name.IsExported() {
|
|
503
|
-
valueSymbols = append(valueSymbols, sanitizeIdentifier(d.Name.Name))
|
|
504
|
-
} else if d.Recv != nil && len(d.Recv.List) == 1 && d.Name.IsExported() {
|
|
505
|
-
recvField := d.Recv.List[0]
|
|
506
|
-
recvTypeExpr := recvField.Type
|
|
507
|
-
if star, ok := recvTypeExpr.(*ast.StarExpr); ok {
|
|
508
|
-
recvTypeExpr = star.X
|
|
509
|
-
}
|
|
510
|
-
if ident, ok := recvTypeExpr.(*ast.Ident); ok {
|
|
511
|
-
typeObj := c.pkg.TypesInfo.ObjectOf(ident)
|
|
512
|
-
if typeObj != nil && typeObj.Exported() {
|
|
513
|
-
if typeName, ok := typeObj.(*types.TypeName); ok {
|
|
514
|
-
underlying := typeName.Type().Underlying()
|
|
515
|
-
if _, isStruct := underlying.(*types.Struct); !isStruct {
|
|
516
|
-
methodName := sanitizeIdentifier(ident.Name + "_" + d.Name.Name)
|
|
517
|
-
valueSymbols = append(valueSymbols, methodName)
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
case *ast.GenDecl:
|
|
524
|
-
for _, spec := range d.Specs {
|
|
525
|
-
switch s := spec.(type) {
|
|
526
|
-
case *ast.TypeSpec:
|
|
527
|
-
if s.Name.IsExported() {
|
|
528
|
-
// Check if this is a struct type
|
|
529
|
-
if _, isStruct := s.Type.(*ast.StructType); isStruct {
|
|
530
|
-
// Structs become TypeScript classes and need both type and value exports
|
|
531
|
-
structSymbols = append(structSymbols, sanitizeIdentifier(s.Name.Name))
|
|
532
|
-
} else {
|
|
533
|
-
// Other type declarations (interfaces, type definitions, type aliases)
|
|
534
|
-
// become TypeScript types and must be exported with "export type"
|
|
535
|
-
typeSymbols = append(typeSymbols, sanitizeIdentifier(s.Name.Name))
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
case *ast.ValueSpec:
|
|
539
|
-
for _, name := range s.Names {
|
|
540
|
-
if name.IsExported() {
|
|
541
|
-
valueSymbols = append(valueSymbols, sanitizeIdentifier(name.Name))
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
break
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Write exports if this file has exported symbols
|
|
552
|
-
if len(valueSymbols) > 0 {
|
|
553
|
-
slices.Sort(valueSymbols)
|
|
554
|
-
exportLine := fmt.Sprintf("export { %s } from %q\n",
|
|
555
|
-
strings.Join(valueSymbols, ", "), translateGeneratedFileToImportPath(fileName))
|
|
556
|
-
if _, err := indexFile.WriteString(exportLine); err != nil {
|
|
557
|
-
return err
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Write struct exports (both as types and values)
|
|
562
|
-
if len(structSymbols) > 0 {
|
|
563
|
-
slices.Sort(structSymbols)
|
|
564
|
-
// Export classes as values (which makes them available as both types and values in TypeScript)
|
|
565
|
-
exportLine := fmt.Sprintf("export { %s } from %q\n",
|
|
566
|
-
strings.Join(structSymbols, ", "), translateGeneratedFileToImportPath(fileName))
|
|
567
|
-
if _, err := indexFile.WriteString(exportLine); err != nil {
|
|
568
|
-
return err
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if len(typeSymbols) > 0 {
|
|
573
|
-
slices.Sort(typeSymbols)
|
|
574
|
-
exportLine := fmt.Sprintf("export type { %s } from %q\n",
|
|
575
|
-
strings.Join(typeSymbols, ", "), translateGeneratedFileToImportPath(fileName))
|
|
576
|
-
if _, err := indexFile.WriteString(exportLine); err != nil {
|
|
577
|
-
return err
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return nil
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// CompileFile handles the compilation of a single Go source file to TypeScript.
|
|
586
|
-
// It uses the pre-computed package-level analysis for accurate TypeScript generation
|
|
587
|
-
// (e.g., about varRefing, async functions, defer statements, receiver usage across files).
|
|
588
|
-
// Then, it creates a `FileCompiler` instance for the file and invokes its
|
|
589
|
-
// `Compile` method to generate the TypeScript code.
|
|
590
|
-
func (p *PackageCompiler) CompileFile(ctx context.Context, name string, syntax *ast.File, analysis *Analysis, packageAnalysis *PackageAnalysis) error {
|
|
591
|
-
fileCompiler, err := NewFileCompiler(p.compilerConf, p.pkg, syntax, name, analysis, packageAnalysis)
|
|
592
|
-
if err != nil {
|
|
593
|
-
return err
|
|
594
|
-
}
|
|
595
|
-
return fileCompiler.Compile(ctx)
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// FileCompiler is responsible for compiling a single Go source file (`ast.File`)
|
|
599
|
-
// into a corresponding TypeScript file. It manages the output file creation,
|
|
600
|
-
// initializes the `TSCodeWriter` for TypeScript code generation, and uses a
|
|
601
|
-
// `GoToTSCompiler` to translate Go declarations and statements.
|
|
602
|
-
type FileCompiler struct {
|
|
603
|
-
compilerConfig *Config
|
|
604
|
-
codeWriter *TSCodeWriter
|
|
605
|
-
pkg *packages.Package
|
|
606
|
-
ast *ast.File
|
|
607
|
-
fullPath string
|
|
608
|
-
Analysis *Analysis
|
|
609
|
-
PackageAnalysis *PackageAnalysis
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// NewFileCompiler creates a new `FileCompiler` for a specific Go file.
|
|
613
|
-
// It takes the global compiler configuration, the Go package information,
|
|
614
|
-
// the AST of the file, its full path, and the pre-computed analysis results.
|
|
615
|
-
// This setup provides all necessary context for translating the file.
|
|
616
|
-
func NewFileCompiler(
|
|
617
|
-
compilerConf *Config,
|
|
618
|
-
pkg *packages.Package,
|
|
619
|
-
astFile *ast.File,
|
|
620
|
-
fullPath string,
|
|
621
|
-
analysis *Analysis,
|
|
622
|
-
packageAnalysis *PackageAnalysis,
|
|
623
|
-
) (*FileCompiler, error) {
|
|
624
|
-
return &FileCompiler{
|
|
625
|
-
compilerConfig: compilerConf,
|
|
626
|
-
pkg: pkg,
|
|
627
|
-
ast: astFile,
|
|
628
|
-
fullPath: fullPath,
|
|
629
|
-
Analysis: analysis,
|
|
630
|
-
PackageAnalysis: packageAnalysis,
|
|
631
|
-
}, nil
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Compile generates the TypeScript code for the Go file.
|
|
635
|
-
// It determines the output TypeScript file path, creates the necessary
|
|
636
|
-
// directories, and opens the output file. It then initializes a `TSCodeWriter`
|
|
637
|
-
// and a `GoToTSCompiler`. A standard import for the `@goscript/builtin`
|
|
638
|
-
// runtime (aliased as `$`) is added, followed by the translation of all
|
|
639
|
-
// top-level declarations in the Go file.
|
|
640
|
-
func (c *FileCompiler) Compile(ctx context.Context) error {
|
|
641
|
-
f := c.ast
|
|
642
|
-
pkgPath := c.pkg.PkgPath
|
|
643
|
-
|
|
644
|
-
outputFilePath := TranslateGoFilePathToTypescriptFilePath(pkgPath, filepath.Base(c.fullPath))
|
|
645
|
-
outputFilePathAbs := filepath.Join(c.compilerConfig.OutputPath, outputFilePath)
|
|
646
|
-
|
|
647
|
-
if err := os.MkdirAll(filepath.Dir(outputFilePathAbs), 0o755); err != nil {
|
|
648
|
-
return err
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
of, err := os.OpenFile(outputFilePathAbs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
|
|
652
|
-
if err != nil {
|
|
653
|
-
return err
|
|
654
|
-
}
|
|
655
|
-
defer of.Close() //nolint:errcheck
|
|
656
|
-
|
|
657
|
-
c.codeWriter = NewTSCodeWriter(of)
|
|
658
|
-
|
|
659
|
-
// Pass analysis to compiler
|
|
660
|
-
goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis, c.fullPath)
|
|
661
|
-
|
|
662
|
-
// Add import for the goscript runtime using namespace import and alias
|
|
663
|
-
c.codeWriter.WriteLinef(
|
|
664
|
-
"import * as $ from %q",
|
|
665
|
-
translateTypescriptModulePathToIndexImportPath("@goscript/builtin"),
|
|
666
|
-
)
|
|
667
|
-
|
|
668
|
-
// Check if there are any .pb.go files in this package and add imports for them
|
|
669
|
-
if err := c.addProtobufImports(); err != nil {
|
|
670
|
-
return fmt.Errorf("failed to add protobuf imports: %w", err)
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Generate auto-imports for functions from other files in the same package
|
|
674
|
-
currentFileName := strings.TrimSuffix(filepath.Base(c.fullPath), ".go")
|
|
675
|
-
if imports := c.PackageAnalysis.FunctionCalls[currentFileName]; imports != nil {
|
|
676
|
-
// Sort source files for consistent import order
|
|
677
|
-
var sourceFiles []string
|
|
678
|
-
for sourceFile := range imports {
|
|
679
|
-
sourceFiles = append(sourceFiles, sourceFile)
|
|
680
|
-
}
|
|
681
|
-
slices.Sort(sourceFiles)
|
|
682
|
-
|
|
683
|
-
for _, sourceFile := range sourceFiles {
|
|
684
|
-
functions := imports[sourceFile]
|
|
685
|
-
if len(functions) > 0 {
|
|
686
|
-
// Apply sanitization to function names
|
|
687
|
-
var sanitizedFunctions []string
|
|
688
|
-
for _, fn := range functions {
|
|
689
|
-
sanitizedFunctions = append(sanitizedFunctions, sanitizeIdentifier(fn))
|
|
690
|
-
}
|
|
691
|
-
// Sort functions for consistent output
|
|
692
|
-
slices.Sort(sanitizedFunctions)
|
|
693
|
-
c.codeWriter.WriteLinef("import { %s } from %q;",
|
|
694
|
-
strings.Join(sanitizedFunctions, ", "), translateGeneratedGoFileToImportPath(sourceFile))
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Generate auto-imports for types from other files in the same package
|
|
700
|
-
if typeImports := c.PackageAnalysis.TypeCalls[currentFileName]; typeImports != nil {
|
|
701
|
-
// Sort source files for consistent import order
|
|
702
|
-
var sourceFiles []string
|
|
703
|
-
for sourceFile := range typeImports {
|
|
704
|
-
sourceFiles = append(sourceFiles, sourceFile)
|
|
705
|
-
}
|
|
706
|
-
slices.Sort(sourceFiles)
|
|
707
|
-
|
|
708
|
-
for _, sourceFile := range sourceFiles {
|
|
709
|
-
typeImports := typeImports[sourceFile]
|
|
710
|
-
if len(typeImports) > 0 {
|
|
711
|
-
// Filter out protobuf types - they should be imported from .pb.ts files, not .gs.ts files
|
|
712
|
-
var nonProtobufTypes []string
|
|
713
|
-
for _, typeName := range typeImports {
|
|
714
|
-
// Check if this type is a protobuf type by looking at its type info
|
|
715
|
-
isProtobuf := false
|
|
716
|
-
if typeObj := c.pkg.Types.Scope().Lookup(typeName); typeObj != nil {
|
|
717
|
-
objType := typeObj.Type()
|
|
718
|
-
if namedType, ok := objType.(*types.Named); ok {
|
|
719
|
-
// Use the same detection logic as codegen
|
|
720
|
-
if goWriter.isProtobufType(namedType) {
|
|
721
|
-
isProtobuf = true
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
if !isProtobuf {
|
|
726
|
-
nonProtobufTypes = append(nonProtobufTypes, typeName)
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
if len(nonProtobufTypes) > 0 {
|
|
731
|
-
// Apply sanitization to type names
|
|
732
|
-
var sanitizedTypes []string
|
|
733
|
-
for _, typeName := range nonProtobufTypes {
|
|
734
|
-
sanitizedTypes = append(sanitizedTypes, sanitizeIdentifier(typeName))
|
|
735
|
-
}
|
|
736
|
-
// Sort types for consistent output
|
|
737
|
-
slices.Sort(sanitizedTypes)
|
|
738
|
-
c.codeWriter.WriteLinef("import { %s } from %q;",
|
|
739
|
-
strings.Join(sanitizedTypes, ", "), translateGeneratedGoFileToImportPath(sourceFile))
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Generate auto-imports for variables from other files in the same package
|
|
746
|
-
if varImports := c.PackageAnalysis.VariableCalls[currentFileName]; varImports != nil {
|
|
747
|
-
// Sort source files for consistent import order
|
|
748
|
-
var sourceFiles []string
|
|
749
|
-
for sourceFile := range varImports {
|
|
750
|
-
sourceFiles = append(sourceFiles, sourceFile)
|
|
751
|
-
}
|
|
752
|
-
slices.Sort(sourceFiles)
|
|
753
|
-
|
|
754
|
-
for _, sourceFile := range sourceFiles {
|
|
755
|
-
variables := varImports[sourceFile]
|
|
756
|
-
if len(variables) > 0 {
|
|
757
|
-
// Apply sanitization to variable names
|
|
758
|
-
var sanitizedVariables []string
|
|
759
|
-
for _, varName := range variables {
|
|
760
|
-
sanitizedVariables = append(sanitizedVariables, sanitizeIdentifier(varName))
|
|
761
|
-
}
|
|
762
|
-
// Sort variables for consistent output
|
|
763
|
-
slices.Sort(sanitizedVariables)
|
|
764
|
-
c.codeWriter.WriteLinef("import { %s } from %q;",
|
|
765
|
-
strings.Join(sanitizedVariables, ", "), translateGeneratedGoFileToImportPath(sourceFile))
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Write synthetic imports (for promoted methods from embedded structs)
|
|
771
|
-
// Use per-file synthetic imports to avoid adding unused imports
|
|
772
|
-
if syntheticImports := c.Analysis.SyntheticImportsPerFile[c.fullPath]; syntheticImports != nil {
|
|
773
|
-
// Sort by package name for consistent output
|
|
774
|
-
var syntheticPkgNames []string
|
|
775
|
-
for pkgName := range syntheticImports {
|
|
776
|
-
syntheticPkgNames = append(syntheticPkgNames, pkgName)
|
|
777
|
-
}
|
|
778
|
-
slices.Sort(syntheticPkgNames)
|
|
779
|
-
for _, pkgName := range syntheticPkgNames {
|
|
780
|
-
imp := syntheticImports[pkgName]
|
|
781
|
-
c.codeWriter.WriteImport(pkgName, translateTypescriptModulePathToIndexImportPath(imp.importPath))
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
c.codeWriter.WriteLine("") // Add a newline after imports
|
|
786
|
-
|
|
787
|
-
if err := goWriter.WriteDecls(f.Decls); err != nil {
|
|
788
|
-
return fmt.Errorf("failed to write declarations: %w", err)
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return nil
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// GoToTSCompiler is the core component responsible for translating Go AST nodes
|
|
795
|
-
// and type information into TypeScript code. It uses a `TSCodeWriter` to output
|
|
796
|
-
// the generated TypeScript and relies on `Analysis` data to make informed
|
|
797
|
-
// decisions about code generation (e.g., varRefing, async behavior).
|
|
798
|
-
type GoToTSCompiler struct {
|
|
799
|
-
tsw *TSCodeWriter
|
|
800
|
-
pkg *packages.Package
|
|
801
|
-
|
|
802
|
-
analysis *Analysis
|
|
803
|
-
|
|
804
|
-
// currentFilePath is the path of the file being compiled
|
|
805
|
-
// Used for looking up per-file synthetic imports
|
|
806
|
-
currentFilePath string
|
|
807
|
-
|
|
808
|
-
// Context flags
|
|
809
|
-
insideAddressOf bool // true when processing operand of & operator
|
|
810
|
-
|
|
811
|
-
// renamedVars tracks variables that have been renamed to avoid type shadowing
|
|
812
|
-
// Key: types.Object of the original variable, Value: new name to use
|
|
813
|
-
renamedVars map[types.Object]string
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// NewGoToTSCompiler creates a new GoToTSCompiler with a TSCodeWriter for output,
|
|
817
|
-
// Go package information, pre-computed analysis results, and the current file path.
|
|
818
|
-
func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, analysis *Analysis, filePath string) *GoToTSCompiler {
|
|
819
|
-
return &GoToTSCompiler{
|
|
820
|
-
tsw: tsw,
|
|
821
|
-
pkg: pkg,
|
|
822
|
-
analysis: analysis,
|
|
823
|
-
currentFilePath: filePath,
|
|
824
|
-
renamedVars: make(map[types.Object]string),
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// objectOfIdent returns the types.Object associated with the identifier.
|
|
829
|
-
// It checks Uses first, then Defs, and returns nil if neither is found.
|
|
830
|
-
func (c *GoToTSCompiler) objectOfIdent(ident *ast.Ident) types.Object {
|
|
831
|
-
if ident == nil || c.pkg == nil || c.pkg.TypesInfo == nil {
|
|
832
|
-
return nil
|
|
833
|
-
}
|
|
834
|
-
if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
835
|
-
return obj
|
|
836
|
-
}
|
|
837
|
-
if obj := c.pkg.TypesInfo.Defs[ident]; obj != nil {
|
|
838
|
-
return obj
|
|
839
|
-
}
|
|
840
|
-
return nil
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// getDeterministicID generates a deterministic unique ID based on file position
|
|
844
|
-
// This replaces the non-deterministic Pos() values to ensure reproducible builds
|
|
845
|
-
func (c *GoToTSCompiler) getDeterministicID(pos token.Pos) string {
|
|
846
|
-
if !pos.IsValid() {
|
|
847
|
-
return "0000"
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// Get file position information
|
|
851
|
-
position := c.pkg.Fset.Position(pos)
|
|
852
|
-
|
|
853
|
-
// Use package path + base filename + line + column for deterministic hashing
|
|
854
|
-
// This avoids absolute path differences between build environments
|
|
855
|
-
baseFilename := filepath.Base(position.Filename)
|
|
856
|
-
if baseFilename == "" {
|
|
857
|
-
baseFilename = "unknown"
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
packagePath := c.pkg.PkgPath
|
|
861
|
-
if packagePath == "" {
|
|
862
|
-
packagePath = "main"
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// Create a string that uniquely identifies this position using only relative/stable info
|
|
866
|
-
positionStr := fmt.Sprintf("%s:%s:%d:%d", packagePath, baseFilename, position.Line, position.Column)
|
|
867
|
-
|
|
868
|
-
// Hash the position string with SHA256
|
|
869
|
-
hash := sha256.Sum256([]byte(positionStr))
|
|
870
|
-
|
|
871
|
-
// Convert to hex and take the last 4 characters (lowercase)
|
|
872
|
-
hexStr := hex.EncodeToString(hash[:])
|
|
873
|
-
return hexStr[len(hexStr)-4:]
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// --- Exported Node-Specific Writers ---
|
|
877
|
-
|
|
878
|
-
// WriteIdent translates a Go identifier (`ast.Ident`) used as a value (e.g.,
|
|
879
|
-
// variable, function name) into its TypeScript equivalent.
|
|
880
|
-
// - If the identifier is `nil`, it writes `null`.
|
|
881
|
-
// - If the identifier refers to a constant, it writes the constant's evaluated value.
|
|
882
|
-
// - Otherwise, it writes the identifier's name.
|
|
883
|
-
// - If `accessVarRefedValue` is true and the analysis (`c.analysis.NeedsVarRefAccess`)
|
|
884
|
-
// indicates the variable is variable referenced, `.value` is appended to access the contained value.
|
|
885
|
-
//
|
|
886
|
-
// This function relies on `go/types` (`TypesInfo.Uses` or `Defs`) to resolve
|
|
887
|
-
// the identifier and the `Analysis` data to determine varRefing needs.
|
|
888
|
-
func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessVarRefedValue bool) {
|
|
889
|
-
if exp.Name == "nil" {
|
|
890
|
-
c.tsw.WriteLiterally("null")
|
|
891
|
-
return
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Check if this identifier has a pre-computed mapping (e.g., wrapper function receiver)
|
|
895
|
-
if mappedName := c.analysis.GetIdentifierMapping(exp); mappedName != "" {
|
|
896
|
-
c.tsw.WriteLiterally(c.sanitizeIdentifier(mappedName))
|
|
897
|
-
return
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// Use TypesInfo to find the object associated with the identifier
|
|
901
|
-
obj := c.objectOfIdent(exp)
|
|
902
|
-
|
|
903
|
-
// Check if this variable has been renamed to avoid type shadowing
|
|
904
|
-
if obj != nil {
|
|
905
|
-
if renamedName, ok := c.renamedVars[obj]; ok {
|
|
906
|
-
c.tsw.WriteLiterally(c.sanitizeIdentifier(renamedName))
|
|
907
|
-
// Determine if we need to access .value based on analysis data
|
|
908
|
-
if accessVarRefedValue && c.analysis.NeedsVarRefAccess(obj) {
|
|
909
|
-
c.tsw.WriteLiterally("!.value")
|
|
910
|
-
}
|
|
911
|
-
return
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// Check if this identifier refers to a constant
|
|
916
|
-
if obj != nil {
|
|
917
|
-
if constObj, isConst := obj.(*types.Const); isConst {
|
|
918
|
-
// Evaluate constants to literals in these cases:
|
|
919
|
-
// 1. Predeclared constants (like iota, true, false) - these have nil package
|
|
920
|
-
// 2. Constants from the current package that are computed expressions
|
|
921
|
-
// (like iota-based constants or mathematical expressions)
|
|
922
|
-
//
|
|
923
|
-
// For simple assignments from imported constants (const x = pkg.Y),
|
|
924
|
-
// preserve the variable name for better readability.
|
|
925
|
-
|
|
926
|
-
if constObj.Pkg() == nil {
|
|
927
|
-
// Predeclared constants - always evaluate to literals
|
|
928
|
-
c.writeConstantValue(constObj)
|
|
929
|
-
return
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if constObj.Pkg() == c.pkg.Types {
|
|
933
|
-
// This is a constant from the current package.
|
|
934
|
-
// Check if it's a simple assignment by looking at the constant value:
|
|
935
|
-
// If the constant has the same name as an identifier in the import,
|
|
936
|
-
// it's likely a simple assignment and we should preserve the name.
|
|
937
|
-
// Otherwise, evaluate to literal (for computed expressions like iota).
|
|
938
|
-
|
|
939
|
-
// For now, let's be conservative and evaluate current package constants
|
|
940
|
-
// to literals to maintain compatibility with existing behavior.
|
|
941
|
-
// This handles iota-based constants correctly.
|
|
942
|
-
c.writeConstantValue(constObj)
|
|
943
|
-
return
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
// Write the identifier name first, sanitizing if it's a reserved word
|
|
949
|
-
c.tsw.WriteLiterally(c.sanitizeIdentifier(exp.Name))
|
|
950
|
-
|
|
951
|
-
// Determine if we need to access .value based on analysis data
|
|
952
|
-
if obj != nil && accessVarRefedValue && c.analysis.NeedsVarRefAccess(obj) {
|
|
953
|
-
c.tsw.WriteLiterally("!.value")
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// WriteCaseClause translates a Go `case` clause (`ast.CaseClause`) from within
|
|
958
|
-
// a `switch` statement into its TypeScript `case` or `default` equivalent.
|
|
959
|
-
// - If `exp.List` is nil, it's a `default` case, written as `default:`.
|
|
960
|
-
// - If `exp.List` is not nil, it's a `case` with one or more match expressions.
|
|
961
|
-
// It's written as `case expr1_ts, expr2_ts, ... :`. (Note: Go's `case`
|
|
962
|
-
// doesn't allow multiple expressions this way, but TypeScript does. This code
|
|
963
|
-
// implies Go's fallthrough is not directly modeled here but rather by explicit
|
|
964
|
-
// `break`s, and each Go `case` becomes one or more TS `case` labels if needed,
|
|
965
|
-
// though current implementation writes them comma-separated which is valid TS syntax).
|
|
966
|
-
// - The body of the case (`exp.Body`) is translated statement by statement using `WriteStmt`.
|
|
967
|
-
// - A `break;` statement is automatically added at the end of the TypeScript case
|
|
968
|
-
// body, because Go `switch` cases have implicit breaks, whereas TypeScript
|
|
969
|
-
// cases fall through by default.
|
|
970
|
-
func (c *GoToTSCompiler) WriteCaseClause(exp *ast.CaseClause) error {
|
|
971
|
-
if exp.List == nil {
|
|
972
|
-
// Default case
|
|
973
|
-
c.tsw.WriteLiterally("default:")
|
|
974
|
-
c.tsw.WriteLine(" {")
|
|
975
|
-
} else {
|
|
976
|
-
// Case with expressions
|
|
977
|
-
// For Go's `case expr1, expr2:`, we translate to:
|
|
978
|
-
// case expr1:
|
|
979
|
-
// case expr2:
|
|
980
|
-
// ... body ...
|
|
981
|
-
// break
|
|
982
|
-
for i, caseExpr := range exp.List {
|
|
983
|
-
c.tsw.WriteLiterally("case ")
|
|
984
|
-
if err := c.WriteValueExpr(caseExpr); err != nil {
|
|
985
|
-
return fmt.Errorf("failed to write case clause expression: %w", err)
|
|
986
|
-
}
|
|
987
|
-
c.tsw.WriteLiterally(":")
|
|
988
|
-
// Only add opening brace after the last case label
|
|
989
|
-
if i == len(exp.List)-1 {
|
|
990
|
-
c.tsw.WriteLine(" {")
|
|
991
|
-
} else {
|
|
992
|
-
c.tsw.WriteLine("")
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// The body is written once, after all case labels for this clause.
|
|
998
|
-
// Wrap in block to provide Go-like case scope semantics.
|
|
999
|
-
c.tsw.Indent(1)
|
|
1000
|
-
for _, stmt := range exp.Body {
|
|
1001
|
-
if err := c.WriteStmt(stmt); err != nil {
|
|
1002
|
-
return fmt.Errorf("failed to write statement in case clause body: %w", err)
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
// Add break statement only if the case body doesn't end with a terminating statement
|
|
1006
|
-
// (return, panic, continue, break, goto, fallthrough). Go's switch has implicit breaks,
|
|
1007
|
-
// but TS needs explicit break - however, adding break after return is unreachable code.
|
|
1008
|
-
if !endsWithTerminatingStmt(exp.Body) {
|
|
1009
|
-
c.tsw.WriteLine("break")
|
|
1010
|
-
}
|
|
1011
|
-
c.tsw.Indent(-1)
|
|
1012
|
-
c.tsw.WriteLine("}")
|
|
1013
|
-
return nil
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// writeChannelReceiveWithOk handles the specific Go assignment pattern
|
|
1017
|
-
// `value, ok := <-channel` (or `:=`).
|
|
1018
|
-
// It translates this into a TypeScript destructuring assignment/declaration
|
|
1019
|
-
// from the result of `await channel_ts.receiveWithOk()`.
|
|
1020
|
-
// The `receiveWithOk()` runtime method is expected to return an object like
|
|
1021
|
-
// `{ value: receivedValue, ok: boolean }`.
|
|
1022
|
-
//
|
|
1023
|
-
// - If `tok` is `token.DEFINE` (for `:=`), it generates `const { value: valueName, ok: okName } = ...`.
|
|
1024
|
-
// - Otherwise (for `=`), it generates `{ value: valueName, ok: okName } = ...` (if not a declaration).
|
|
1025
|
-
// - Blank identifiers (`_`) on the LHS are handled:
|
|
1026
|
-
// - If `value` is blank: `const { ok: okName } = ...` or `{ ok: okName } = ...`.
|
|
1027
|
-
// - If `ok` is blank: `const { value: valueName } = ...` or `{ value: valueName } = ...`.
|
|
1028
|
-
// - If both are blank, it simply writes `await channel_ts.receiveWithOk()` to
|
|
1029
|
-
// execute the receive for its potential side effects (though `receiveWithOk`
|
|
1030
|
-
// is primarily for its return values) and discards the result.
|
|
1031
|
-
//
|
|
1032
|
-
// This ensures that the Go channel receive with existence check is correctly
|
|
1033
|
-
// mapped to the asynchronous TypeScript channel helper.
|
|
1034
|
-
func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *ast.UnaryExpr, tok token.Token) error {
|
|
1035
|
-
// Ensure LHS has exactly two expressions
|
|
1036
|
-
if len(lhs) != 2 {
|
|
1037
|
-
return fmt.Errorf("internal error: writeChannelReceiveWithOk called with %d LHS expressions", len(lhs))
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// Get variable names, handling blank identifiers
|
|
1041
|
-
valueIsBlank := false
|
|
1042
|
-
okIsBlank := false
|
|
1043
|
-
var valueName string
|
|
1044
|
-
var okName string
|
|
1045
|
-
|
|
1046
|
-
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
1047
|
-
if valIdent.Name == "_" {
|
|
1048
|
-
valueIsBlank = true
|
|
1049
|
-
} else {
|
|
1050
|
-
valueName = valIdent.Name
|
|
1051
|
-
}
|
|
1052
|
-
} else {
|
|
1053
|
-
return fmt.Errorf("unhandled LHS expression type for value in channel receive: %T", lhs[0])
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
if okIdent, ok := lhs[1].(*ast.Ident); ok {
|
|
1057
|
-
if okIdent.Name == "_" {
|
|
1058
|
-
okIsBlank = true
|
|
1059
|
-
} else {
|
|
1060
|
-
okName = okIdent.Name
|
|
1061
|
-
}
|
|
1062
|
-
} else {
|
|
1063
|
-
return fmt.Errorf("unhandled LHS expression type for ok in channel receive: %T", lhs[1])
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// Generate destructuring assignment/declaration for val, ok := <-channel
|
|
1067
|
-
keyword := ""
|
|
1068
|
-
if tok == token.DEFINE {
|
|
1069
|
-
keyword = "const " // Use const for destructuring declaration
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// Build the destructuring pattern, handling blank identifiers correctly for TS
|
|
1073
|
-
patternParts := []string{}
|
|
1074
|
-
if !valueIsBlank {
|
|
1075
|
-
// Map the 'value' field to the Go variable name
|
|
1076
|
-
patternParts = append(patternParts, fmt.Sprintf("value: %s", valueName))
|
|
1077
|
-
} else {
|
|
1078
|
-
// If both are blank, just await the call and return
|
|
1079
|
-
if okIsBlank {
|
|
1080
|
-
c.tsw.WriteLiterally("await $.chanRecvWithOk(")
|
|
1081
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
1082
|
-
return fmt.Errorf("failed to write channel expression in receive: %w", err)
|
|
1083
|
-
}
|
|
1084
|
-
c.tsw.WriteLiterally(")")
|
|
1085
|
-
c.tsw.WriteLine("")
|
|
1086
|
-
return nil // Nothing to assign
|
|
1087
|
-
}
|
|
1088
|
-
// Only value is blank, need to map 'ok' property
|
|
1089
|
-
patternParts = append(patternParts, fmt.Sprintf("ok: %s", okName))
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
if !okIsBlank && !valueIsBlank { // Both are present
|
|
1093
|
-
patternParts = append(patternParts, fmt.Sprintf("ok: %s", okName))
|
|
1094
|
-
}
|
|
1095
|
-
// If both are blank, patternParts remains empty, handled earlier.
|
|
1096
|
-
|
|
1097
|
-
destructuringPattern := fmt.Sprintf("{ %s }", strings.Join(patternParts, ", "))
|
|
1098
|
-
|
|
1099
|
-
// Write the destructuring assignment/declaration
|
|
1100
|
-
c.tsw.WriteLiterally(keyword) // "const " or ""
|
|
1101
|
-
c.tsw.WriteLiterally(destructuringPattern)
|
|
1102
|
-
c.tsw.WriteLiterally(" = await $.chanRecvWithOk(")
|
|
1103
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
1104
|
-
return fmt.Errorf("failed to write channel expression in receive: %w", err)
|
|
1105
|
-
}
|
|
1106
|
-
c.tsw.WriteLiterally(")")
|
|
1107
|
-
c.tsw.WriteLine("")
|
|
1108
|
-
|
|
1109
|
-
return nil
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
// WriteDoc translates a Go comment group (`ast.CommentGroup`) into TypeScript comments,
|
|
1113
|
-
// preserving the original style (line `//` or block `/* ... */`).
|
|
1114
|
-
// - If `doc` is nil, it does nothing.
|
|
1115
|
-
// - It iterates through each `ast.Comment` in the group.
|
|
1116
|
-
// - If a comment starts with `//`, it's written as a TypeScript line comment.
|
|
1117
|
-
// - If a comment starts with `/*`, it's written as a TypeScript block comment:
|
|
1118
|
-
// - Single-line block comments (`/* comment */`) are kept on one line.
|
|
1119
|
-
// - Multi-line block comments are formatted with `/*` on its own line,
|
|
1120
|
-
// each content line prefixed with ` * `, and ` */` on its own line.
|
|
1121
|
-
//
|
|
1122
|
-
// This function helps maintain documentation and explanatory comments from the
|
|
1123
|
-
// Go source in the generated TypeScript code.
|
|
1124
|
-
func (c *GoToTSCompiler) WriteDoc(doc *ast.CommentGroup) {
|
|
1125
|
-
if doc == nil {
|
|
1126
|
-
return
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
for _, comment := range doc.List {
|
|
1130
|
-
// Preserve original comment style (// or /*)
|
|
1131
|
-
if strings.HasPrefix(comment.Text, "//") {
|
|
1132
|
-
c.tsw.WriteLine(comment.Text)
|
|
1133
|
-
} else if after, ok := strings.CutPrefix(comment.Text, "/*"); ok {
|
|
1134
|
-
// Write block comments potentially spanning multiple lines
|
|
1135
|
-
// Remove /* and */, then split by newline
|
|
1136
|
-
content := strings.TrimSuffix(after, "*/")
|
|
1137
|
-
lines := strings.Split(content, "\n") // Use \n as Split expects a separator string
|
|
1138
|
-
|
|
1139
|
-
if len(lines) == 1 && !strings.Contains(lines[0], "\n") { // Check again for internal newlines just in case
|
|
1140
|
-
// Keep single-line block comments on one line
|
|
1141
|
-
c.tsw.WriteLinef("/*%s*/", lines[0])
|
|
1142
|
-
} else {
|
|
1143
|
-
// Write multi-line block comments
|
|
1144
|
-
c.tsw.WriteLine("/*")
|
|
1145
|
-
for _, line := range lines {
|
|
1146
|
-
// WriteLine handles indentation preamble automatically
|
|
1147
|
-
c.tsw.WriteLine(" *" + line) // Add conventional * prefix
|
|
1148
|
-
}
|
|
1149
|
-
c.tsw.WriteLine(" */")
|
|
1150
|
-
}
|
|
1151
|
-
} else {
|
|
1152
|
-
// Should not happen for valid Go comments, but handle defensively
|
|
1153
|
-
c.tsw.WriteCommentLine(" Unknown comment format: " + comment.Text)
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// sanitizeIdentifier is a method wrapper around the package-level sanitizeIdentifier function
|
|
1159
|
-
func (c *GoToTSCompiler) sanitizeIdentifier(name string) string {
|
|
1160
|
-
return sanitizeIdentifier(name)
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
// writeConstantValue writes the evaluated value of a Go constant to TypeScript.
|
|
1164
|
-
// It handles different constant types (integer, float, string, boolean, complex)
|
|
1165
|
-
// and writes the appropriate TypeScript literal.
|
|
1166
|
-
func (c *GoToTSCompiler) writeConstantValue(constObj *types.Const) {
|
|
1167
|
-
val := constObj.Val()
|
|
1168
|
-
|
|
1169
|
-
switch val.Kind() {
|
|
1170
|
-
case constant.Int:
|
|
1171
|
-
// For integer constants, write the string representation
|
|
1172
|
-
c.tsw.WriteLiterally(val.String())
|
|
1173
|
-
case constant.Float:
|
|
1174
|
-
// For float constants, write the string representation
|
|
1175
|
-
c.tsw.WriteLiterally(val.String())
|
|
1176
|
-
case constant.String:
|
|
1177
|
-
// For string constants, write as a quoted string literal
|
|
1178
|
-
// Use constant.StringVal to get the full string value without truncation,
|
|
1179
|
-
// then manually add quotes since StringVal returns the unquoted string
|
|
1180
|
-
stringValue := constant.StringVal(val)
|
|
1181
|
-
c.tsw.WriteLiterallyf("%q", stringValue) // %q adds proper quotes and escaping
|
|
1182
|
-
case constant.Bool:
|
|
1183
|
-
// For boolean constants, write true/false
|
|
1184
|
-
if constant.BoolVal(val) {
|
|
1185
|
-
c.tsw.WriteLiterally("true")
|
|
1186
|
-
} else {
|
|
1187
|
-
c.tsw.WriteLiterally("false")
|
|
1188
|
-
}
|
|
1189
|
-
case constant.Complex:
|
|
1190
|
-
// For complex constants, we need to handle them specially
|
|
1191
|
-
// For now, write as a comment indicating unsupported
|
|
1192
|
-
c.tsw.WriteLiterally("/* complex constant: " + val.String() + " */")
|
|
1193
|
-
default:
|
|
1194
|
-
// For unknown constant types, write as a comment
|
|
1195
|
-
c.tsw.WriteLiterally("/* unknown constant: " + val.String() + " */")
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// copyEmbeddedPackage recursively copies files from an embedded FS path to a filesystem directory.
|
|
1200
|
-
// It handles both regular files and directories, but only copies .gs.ts and .ts files.
|
|
1201
|
-
// It preserves existing subdirectories that aren't being overwritten.
|
|
1202
|
-
func (c *Compiler) copyEmbeddedPackage(embeddedPath string, outputPath string) error {
|
|
1203
|
-
// Create the output path if it doesn't exist
|
|
1204
|
-
if err := os.MkdirAll(outputPath, 0o755); err != nil {
|
|
1205
|
-
return fmt.Errorf("failed to create output directory %s: %w", outputPath, err)
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
// List the entries in the embedded path
|
|
1209
|
-
entries, err := gs.GsOverrides.ReadDir(embeddedPath)
|
|
1210
|
-
if err != nil {
|
|
1211
|
-
return fmt.Errorf("failed to read embedded directory %s: %w", embeddedPath, err)
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// Process each entry
|
|
1215
|
-
for _, entry := range entries {
|
|
1216
|
-
entryPath := filepath.Join(embeddedPath, entry.Name())
|
|
1217
|
-
outputEntryPath := filepath.Join(outputPath, entry.Name())
|
|
1218
|
-
|
|
1219
|
-
if entry.IsDir() {
|
|
1220
|
-
// Create the output directory
|
|
1221
|
-
if err := os.MkdirAll(outputEntryPath, 0o755); err != nil {
|
|
1222
|
-
return fmt.Errorf("failed to create output directory %s: %w", outputEntryPath, err)
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// Recursively copy the directory contents
|
|
1226
|
-
if err := c.copyEmbeddedPackage(entryPath, outputEntryPath); err != nil {
|
|
1227
|
-
return err
|
|
1228
|
-
}
|
|
1229
|
-
} else {
|
|
1230
|
-
// Only copy .gs.ts and .ts files, skip .go files and others
|
|
1231
|
-
fileName := entry.Name()
|
|
1232
|
-
if !strings.HasSuffix(fileName, ".gs.ts") && !strings.HasSuffix(fileName, ".ts") {
|
|
1233
|
-
// c.le.Debugf("Skipping non-TypeScript file: %s", fileName)
|
|
1234
|
-
continue
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// Skip .test.ts files
|
|
1238
|
-
if strings.HasSuffix(fileName, ".test.ts") {
|
|
1239
|
-
// c.le.Debugf("Skipping test file: %s", fileName)
|
|
1240
|
-
continue
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
// Remove existing file if it exists (but preserve directories)
|
|
1244
|
-
if stat, err := os.Stat(outputEntryPath); err == nil && !stat.IsDir() {
|
|
1245
|
-
if err := os.Remove(outputEntryPath); err != nil {
|
|
1246
|
-
return fmt.Errorf("failed to remove existing file %s: %w", outputEntryPath, err)
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// Read the file content from the embedded FS
|
|
1251
|
-
content, err := gs.GsOverrides.ReadFile(entryPath)
|
|
1252
|
-
if err != nil {
|
|
1253
|
-
return fmt.Errorf("failed to read embedded file %s: %w", entryPath, err)
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
// Write the content to the output file
|
|
1257
|
-
if err := os.WriteFile(outputEntryPath, content, 0o644); err != nil {
|
|
1258
|
-
return fmt.Errorf("failed to write file %s: %w", outputEntryPath, err)
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
return nil
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
// GsPackageMetadata holds metadata about a gs/ package
|
|
1267
|
-
type GsPackageMetadata struct {
|
|
1268
|
-
// Dependencies lists the import paths that this gs/ package requires
|
|
1269
|
-
Dependencies []string `json:"dependencies,omitempty"`
|
|
1270
|
-
AsyncMethods map[string]bool `json:"asyncMethods,omitempty"`
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// ReadGsPackageMetadata reads dependency metadata from meta.json file in a gs/ package
|
|
1274
|
-
func (c *Compiler) ReadGsPackageMetadata(gsSourcePath string) (*GsPackageMetadata, error) {
|
|
1275
|
-
metadata := &GsPackageMetadata{
|
|
1276
|
-
Dependencies: []string{},
|
|
1277
|
-
AsyncMethods: make(map[string]bool),
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
// Try to read meta.json file
|
|
1281
|
-
metaFilePath := filepath.Join(gsSourcePath, "meta.json")
|
|
1282
|
-
content, err := gs.GsOverrides.ReadFile(metaFilePath)
|
|
1283
|
-
if err != nil {
|
|
1284
|
-
// Only treat missing file as "no metadata"; surface other errors
|
|
1285
|
-
if os.IsNotExist(err) {
|
|
1286
|
-
return metadata, nil
|
|
1287
|
-
}
|
|
1288
|
-
return nil, fmt.Errorf("failed to read meta.json in %s: %w", gsSourcePath, err)
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
// Parse the JSON content
|
|
1292
|
-
if err := json.Unmarshal(content, metadata); err != nil {
|
|
1293
|
-
return metadata, fmt.Errorf("failed to parse meta.json in %s: %w", gsSourcePath, err)
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
return metadata, nil
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
// copyGsPackageWithDependencies copies a gs/ package and all its dependencies recursively
|
|
1300
|
-
// It tracks already processed packages to avoid infinite loops and duplicate work
|
|
1301
|
-
func (c *Compiler) copyGsPackageWithDependencies(packagePath string, processedPackages map[string]bool, result *CompilationResult) error {
|
|
1302
|
-
// Check if we've already processed this package
|
|
1303
|
-
if processedPackages[packagePath] {
|
|
1304
|
-
return nil
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// Mark this package as being processed
|
|
1308
|
-
processedPackages[packagePath] = true
|
|
1309
|
-
|
|
1310
|
-
gsSourcePath := "gs/" + packagePath
|
|
1311
|
-
|
|
1312
|
-
// Check if the gs package actually exists
|
|
1313
|
-
_, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
|
|
1314
|
-
if gsErr != nil {
|
|
1315
|
-
if os.IsNotExist(gsErr) {
|
|
1316
|
-
c.le.Debugf("gs package %s does not exist, skipping", packagePath)
|
|
1317
|
-
return nil
|
|
1318
|
-
}
|
|
1319
|
-
return gsErr
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
// Read metadata to get dependencies
|
|
1323
|
-
metadata, err := c.ReadGsPackageMetadata(gsSourcePath)
|
|
1324
|
-
if err != nil {
|
|
1325
|
-
// Surface metadata errors instead of silently continuing
|
|
1326
|
-
return fmt.Errorf("failed to read metadata for gs package %s: %w", packagePath, err)
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
// Log dependencies if any are found
|
|
1330
|
-
/*
|
|
1331
|
-
if len(metadata.Dependencies) > 0 {
|
|
1332
|
-
c.le.Debugf("Package %s has dependencies: %v", packagePath, metadata.Dependencies)
|
|
1333
|
-
}
|
|
1334
|
-
*/
|
|
1335
|
-
|
|
1336
|
-
// First, recursively process all dependencies
|
|
1337
|
-
for _, depPath := range metadata.Dependencies {
|
|
1338
|
-
if err := c.copyGsPackageWithDependencies(depPath, processedPackages, result); err != nil {
|
|
1339
|
-
return fmt.Errorf("failed to copy dependency %s of package %s: %w", depPath, packagePath, err)
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// Now copy the package itself
|
|
1344
|
-
// c.le.Debugf("Copying handwritten package %s to output directory", packagePath)
|
|
1345
|
-
|
|
1346
|
-
// Compute output path for this package
|
|
1347
|
-
outputPath := ComputeModulePath(c.config.OutputPath, packagePath)
|
|
1348
|
-
|
|
1349
|
-
// Create the output directory
|
|
1350
|
-
if err := os.MkdirAll(outputPath, 0o755); err != nil {
|
|
1351
|
-
return fmt.Errorf("failed to create output directory for %s: %w", packagePath, err)
|
|
32
|
+
if err := ctx.Err(); err != nil {
|
|
33
|
+
return nil, err
|
|
1352
34
|
}
|
|
1353
35
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
return fmt.Errorf("failed to copy embedded package %s: %w", packagePath, err)
|
|
36
|
+
if c.le != nil {
|
|
37
|
+
c.le.Debugf("goscript v2 compile request: %v", patterns)
|
|
1357
38
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
return nil
|
|
39
|
+
request := c.service.RequestOwner().NewRequest(c.config, patterns)
|
|
40
|
+
return c.service.Compile(ctx, request)
|
|
1361
41
|
}
|