goscript 0.0.70 → 0.0.73
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/cmd/goscript-wasm/main.go +46 -0
- package/compiler/analysis.go +4 -4
- package/compiler/compiler.go +69 -1
- package/compiler/composite-lit.go +8 -1
- package/compiler/decl.go +93 -0
- package/compiler/expr-call-type-conversion.go +8 -1
- package/compiler/expr.go +1 -1
- package/compiler/spec.go +7 -2
- package/compiler/stmt-assign.go +6 -4
- package/compiler/type.go +51 -0
- package/compiler/wasm/compile.go +12 -0
- package/compiler/wasm_api.go +160 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +2 -4
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/reflect/map.js +1 -1
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/strings/search.js.map +1 -1
- package/go.mod +3 -3
- package/go.sum +6 -10
- package/gs/builtin/slice.ts +20 -9
- package/gs/builtin/type.ts +22 -21
- package/gs/reflect/map.ts +8 -1
- package/gs/reflect/type.ts +9 -3
- package/gs/strings/search.ts +0 -1
- package/package.json +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//go:build js && wasm
|
|
2
|
+
|
|
3
|
+
package main
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"syscall/js"
|
|
7
|
+
|
|
8
|
+
"github.com/aperturerobotics/goscript/compiler/wasm"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func main() {
|
|
12
|
+
// Register the compile function as a global JavaScript function
|
|
13
|
+
js.Global().Set("goscriptCompile", js.FuncOf(compileWrapper))
|
|
14
|
+
|
|
15
|
+
// Keep the program running
|
|
16
|
+
select {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// compileWrapper wraps the compile function for JavaScript interop
|
|
20
|
+
func compileWrapper(this js.Value, args []js.Value) interface{} {
|
|
21
|
+
if len(args) < 1 {
|
|
22
|
+
return map[string]interface{}{
|
|
23
|
+
"error": "missing source code argument",
|
|
24
|
+
"output": "",
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
source := args[0].String()
|
|
29
|
+
packageName := "main"
|
|
30
|
+
if len(args) > 1 {
|
|
31
|
+
packageName = args[1].String()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
output, err := wasm.CompileSource(source, packageName)
|
|
35
|
+
if err != nil {
|
|
36
|
+
return map[string]interface{}{
|
|
37
|
+
"error": err.Error(),
|
|
38
|
+
"output": "",
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return map[string]interface{}{
|
|
43
|
+
"error": "",
|
|
44
|
+
"output": output,
|
|
45
|
+
}
|
|
46
|
+
}
|
package/compiler/analysis.go
CHANGED
|
@@ -221,10 +221,10 @@ func NewAnalysis(allPackages map[string]*packages.Package) *Analysis {
|
|
|
221
221
|
AsyncReturningVars: make(map[types.Object]bool),
|
|
222
222
|
NamedBasicTypes: make(map[types.Type]bool),
|
|
223
223
|
AllPackages: allPackages,
|
|
224
|
-
InterfaceImplementations:
|
|
225
|
-
MethodAsyncStatus:
|
|
226
|
-
ReferencedTypesPerFile:
|
|
227
|
-
SyntheticImportsPerFile:
|
|
224
|
+
InterfaceImplementations: make(map[InterfaceMethodKey][]ImplementationInfo),
|
|
225
|
+
MethodAsyncStatus: make(map[MethodKey]bool),
|
|
226
|
+
ReferencedTypesPerFile: make(map[string]map[*types.Named]bool),
|
|
227
|
+
SyntheticImportsPerFile: make(map[string]map[string]*fileImport),
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
|
package/compiler/compiler.go
CHANGED
|
@@ -17,6 +17,7 @@ import (
|
|
|
17
17
|
|
|
18
18
|
gs "github.com/aperturerobotics/goscript"
|
|
19
19
|
"github.com/sirupsen/logrus"
|
|
20
|
+
"golang.org/x/mod/modfile"
|
|
20
21
|
"golang.org/x/tools/go/packages"
|
|
21
22
|
)
|
|
22
23
|
|
|
@@ -90,6 +91,67 @@ type CompilationResult struct {
|
|
|
90
91
|
OriginalPackages []string
|
|
91
92
|
}
|
|
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
|
+
|
|
93
155
|
// CompilePackages loads Go packages based on the provided patterns and
|
|
94
156
|
// then compiles each loaded package into TypeScript. It uses the context for
|
|
95
157
|
// cancellation and applies the compiler's configured options during package loading.
|
|
@@ -102,9 +164,15 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*Co
|
|
|
102
164
|
opts := c.opts
|
|
103
165
|
opts.Context = ctx
|
|
104
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
|
+
|
|
105
173
|
// First, load the initial packages with NeedImports to get all dependencies
|
|
106
174
|
opts.Mode |= packages.NeedImports
|
|
107
|
-
pkgs, err := packages.Load(&opts,
|
|
175
|
+
pkgs, err := packages.Load(&opts, resolvedPatterns...)
|
|
108
176
|
if err != nil {
|
|
109
177
|
return nil, fmt.Errorf("failed to load packages: %w", err)
|
|
110
178
|
}
|
|
@@ -38,7 +38,14 @@ func (c *GoToTSCompiler) WriteCompositeLit(exp *ast.CompositeLit) error {
|
|
|
38
38
|
|
|
39
39
|
if exp.Type != nil {
|
|
40
40
|
// Handle map literals: map[K]V{k1: v1, k2: v2}
|
|
41
|
-
|
|
41
|
+
// Also handle type alias for map: type OpNames map[K]V; OpNames{k1: v1}
|
|
42
|
+
isMapType := false
|
|
43
|
+
if _, astMapType := exp.Type.(*ast.MapType); astMapType {
|
|
44
|
+
isMapType = true
|
|
45
|
+
} else if litType != nil {
|
|
46
|
+
_, isMapType = litType.Underlying().(*types.Map)
|
|
47
|
+
}
|
|
48
|
+
if isMapType {
|
|
42
49
|
c.tsw.WriteLiterally("new Map([")
|
|
43
50
|
|
|
44
51
|
// Add each key-value pair as an entry
|
package/compiler/decl.go
CHANGED
|
@@ -6,8 +6,42 @@ import (
|
|
|
6
6
|
"go/token"
|
|
7
7
|
"go/types"
|
|
8
8
|
"slices"
|
|
9
|
+
"strings"
|
|
9
10
|
)
|
|
10
11
|
|
|
12
|
+
// linknameInfo holds parsed //go:linkname directive information.
|
|
13
|
+
type linknameInfo struct {
|
|
14
|
+
targetPkg string // target package path (e.g., "github.com/example/package")
|
|
15
|
+
targetName string // target symbol name (e.g., "CanHaveDecorators")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// parseLinknameDirective checks if a doc comment contains a //go:linkname directive
|
|
19
|
+
// and returns the parsed information if found.
|
|
20
|
+
func parseLinknameDirective(doc *ast.CommentGroup) *linknameInfo {
|
|
21
|
+
if doc == nil {
|
|
22
|
+
return nil
|
|
23
|
+
}
|
|
24
|
+
for _, comment := range doc.List {
|
|
25
|
+
text := strings.TrimSpace(comment.Text)
|
|
26
|
+
if strings.HasPrefix(text, "//go:linkname ") {
|
|
27
|
+
// Format: //go:linkname localname importpath.name
|
|
28
|
+
parts := strings.Fields(text)
|
|
29
|
+
if len(parts) >= 3 {
|
|
30
|
+
target := parts[2]
|
|
31
|
+
// Split target into package path and symbol name
|
|
32
|
+
lastDot := strings.LastIndex(target, ".")
|
|
33
|
+
if lastDot > 0 {
|
|
34
|
+
return &linknameInfo{
|
|
35
|
+
targetPkg: target[:lastDot],
|
|
36
|
+
targetName: target[lastDot+1:],
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
// WriteDecls iterates through a slice of Go top-level declarations (`ast.Decl`)
|
|
12
46
|
// and translates each one into its TypeScript equivalent.
|
|
13
47
|
// It distinguishes between:
|
|
@@ -436,6 +470,12 @@ func (c *GoToTSCompiler) WriteFuncDeclAsFunction(decl *ast.FuncDecl) error {
|
|
|
436
470
|
return nil
|
|
437
471
|
}
|
|
438
472
|
|
|
473
|
+
// Check for //go:linkname directive on functions without a body
|
|
474
|
+
if linkInfo := parseLinknameDirective(decl.Doc); linkInfo != nil && decl.Body == nil {
|
|
475
|
+
// This is a linkname function - generate a re-export with type annotation
|
|
476
|
+
return c.writeLinknameFunction(decl, linkInfo)
|
|
477
|
+
}
|
|
478
|
+
|
|
439
479
|
if decl.Doc != nil {
|
|
440
480
|
c.WriteDoc(decl.Doc)
|
|
441
481
|
}
|
|
@@ -495,6 +535,59 @@ func (c *GoToTSCompiler) WriteFuncDeclAsFunction(decl *ast.FuncDecl) error {
|
|
|
495
535
|
return nil
|
|
496
536
|
}
|
|
497
537
|
|
|
538
|
+
// writeLinknameFunction generates a TypeScript const re-export for a //go:linkname function.
|
|
539
|
+
// It generates: export const LocalName: (params) => ReturnType = alias.TargetName
|
|
540
|
+
//
|
|
541
|
+
// Unlike Go's //go:linkname, which does not perform type checking between the
|
|
542
|
+
// local declaration and the target symbol, the generated TypeScript code includes an
|
|
543
|
+
// explicit function type annotation. This means the TypeScript compiler will enforce
|
|
544
|
+
// that the declared parameter and return types match the actual target function type.
|
|
545
|
+
// Any mismatch will result in a TypeScript compilation error.
|
|
546
|
+
func (c *GoToTSCompiler) writeLinknameFunction(decl *ast.FuncDecl, info *linknameInfo) error {
|
|
547
|
+
// Find the import alias for the target package
|
|
548
|
+
alias := c.findImportAlias(info.targetPkg)
|
|
549
|
+
if alias == "" {
|
|
550
|
+
// Package not imported, write a comment and fall back to empty function
|
|
551
|
+
c.tsw.WriteLinef("//go:linkname target %s not found in imports", info.targetPkg)
|
|
552
|
+
c.tsw.WriteLiterally("export function ")
|
|
553
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(decl.Name.Name))
|
|
554
|
+
c.WriteFuncType(decl.Type, false)
|
|
555
|
+
c.tsw.WriteLine(" {}")
|
|
556
|
+
return nil
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Generate: export const LocalName: (params) => ReturnType = alias.TargetName
|
|
560
|
+
c.tsw.WriteLiterally("export const ")
|
|
561
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(decl.Name.Name))
|
|
562
|
+
c.tsw.WriteLiterally(": ")
|
|
563
|
+
|
|
564
|
+
// Write the function type as an arrow function type
|
|
565
|
+
c.WriteFuncTypeAsArrow(decl.Type)
|
|
566
|
+
|
|
567
|
+
c.tsw.WriteLiterally(" = ")
|
|
568
|
+
c.tsw.WriteLiterally(alias)
|
|
569
|
+
c.tsw.WriteLiterally(".")
|
|
570
|
+
c.tsw.WriteLiterally(info.targetName)
|
|
571
|
+
c.tsw.WriteLine("")
|
|
572
|
+
|
|
573
|
+
return nil
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// findImportAlias finds the import alias used for a Go package path in the current file.
|
|
577
|
+
// Returns empty string if the package is not imported.
|
|
578
|
+
func (c *GoToTSCompiler) findImportAlias(pkgPath string) string {
|
|
579
|
+
// The importPath in analysis.Imports is stored as the TypeScript path (e.g., @goscript/pkg/path)
|
|
580
|
+
// We need to convert the Go package path to match
|
|
581
|
+
tsPath := "@goscript/" + pkgPath
|
|
582
|
+
|
|
583
|
+
for alias, imp := range c.analysis.Imports {
|
|
584
|
+
if imp.importPath == tsPath {
|
|
585
|
+
return alias
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return ""
|
|
589
|
+
}
|
|
590
|
+
|
|
498
591
|
// WriteFuncDeclAsMethod translates a Go function declaration (`ast.FuncDecl`)
|
|
499
592
|
// that has a receiver (i.e., it's a method) into a TypeScript class method.
|
|
500
593
|
// - It preserves Go documentation comments (`decl.Doc`).
|
|
@@ -264,7 +264,14 @@ func (c *GoToTSCompiler) writeStringConversion(exp *ast.CallExpr) error {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
// Fallback: when type info is not available (e.g., in WASM context),
|
|
268
|
+
// use a generic conversion that handles both []byte and string
|
|
269
|
+
c.tsw.WriteLiterally("$.genericBytesOrStringToString(")
|
|
270
|
+
if err := c.WriteValueExpr(arg); err != nil {
|
|
271
|
+
return fmt.Errorf("failed to write argument for string(unknown type) conversion: %w", err)
|
|
272
|
+
}
|
|
273
|
+
c.tsw.WriteLiterally(")")
|
|
274
|
+
return nil
|
|
268
275
|
}
|
|
269
276
|
|
|
270
277
|
// handleTypeConversionCommon contains the shared logic for type conversions
|
package/compiler/expr.go
CHANGED
|
@@ -566,7 +566,7 @@ func (c *GoToTSCompiler) WriteUnaryExpr(exp *ast.UnaryExpr) error {
|
|
|
566
566
|
}
|
|
567
567
|
if obj != nil && c.analysis.NeedsVarRef(obj) {
|
|
568
568
|
// &varRefVar -> varRefVar (the variable reference itself)
|
|
569
|
-
c.tsw.WriteLiterally(ident.Name) // Write the identifier name (which holds the variable reference)
|
|
569
|
+
c.tsw.WriteLiterally(c.sanitizeIdentifier(ident.Name)) // Write the identifier name (which holds the variable reference)
|
|
570
570
|
return nil
|
|
571
571
|
}
|
|
572
572
|
}
|
package/compiler/spec.go
CHANGED
|
@@ -602,12 +602,17 @@ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
|
|
|
602
602
|
|
|
603
603
|
// Determine the import name to use in TypeScript
|
|
604
604
|
var impName string
|
|
605
|
-
if a.Name != nil && a.Name.Name != "" {
|
|
605
|
+
if a.Name != nil && a.Name.Name != "" && a.Name.Name != "." && a.Name.Name != "_" {
|
|
606
606
|
// Explicit alias provided: import alias "path/to/pkg"
|
|
607
|
+
// Skip dot imports (. "pkg") and blank imports (_ "pkg")
|
|
607
608
|
impName = a.Name.Name
|
|
609
|
+
} else if a.Name != nil && a.Name.Name == "_" {
|
|
610
|
+
// Blank import (_ "pkg") - skip entirely, these are for side effects only
|
|
611
|
+
return
|
|
608
612
|
} else {
|
|
609
|
-
// No explicit alias, use the actual package name from type information
|
|
613
|
+
// No explicit alias, or dot import - use the actual package name from type information
|
|
610
614
|
// This handles cases where package name differs from the last path segment
|
|
615
|
+
// For dot imports, we still need a valid identifier for the import
|
|
611
616
|
if actualName, err := getActualPackageName(goPath, c.pkg.Imports); err == nil {
|
|
612
617
|
impName = actualName
|
|
613
618
|
} else {
|
package/compiler/stmt-assign.go
CHANGED
|
@@ -310,10 +310,12 @@ func (c *GoToTSCompiler) writeMultiVarAssignFromCall(lhs []ast.Expr, callExpr *a
|
|
|
310
310
|
// Get function return types if available
|
|
311
311
|
var resultTypes []*types.Var
|
|
312
312
|
if callExpr.Fun != nil {
|
|
313
|
-
if
|
|
314
|
-
if funcType
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
if funType := c.pkg.TypesInfo.TypeOf(callExpr.Fun); funType != nil {
|
|
314
|
+
if funcType, ok := funType.Underlying().(*types.Signature); ok {
|
|
315
|
+
if funcType.Results() != nil && funcType.Results().Len() > 0 {
|
|
316
|
+
for i := 0; i < funcType.Results().Len(); i++ {
|
|
317
|
+
resultTypes = append(resultTypes, funcType.Results().At(i))
|
|
318
|
+
}
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
}
|
package/compiler/type.go
CHANGED
|
@@ -447,6 +447,57 @@ func (c *GoToTSCompiler) WriteFuncType(exp *ast.FuncType, isAsync bool) {
|
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
// WriteFuncTypeAsArrow writes a function type using arrow function syntax.
|
|
451
|
+
// This is used for type annotations where we need (params) => ReturnType format
|
|
452
|
+
// instead of function(params): ReturnType format.
|
|
453
|
+
// Example: (name: string, age: number) => boolean
|
|
454
|
+
//
|
|
455
|
+
// This helper does not perform any async detection and never wraps the
|
|
456
|
+
// return type in Promise<>. The generated arrow function type reflects the
|
|
457
|
+
// Go function's translated result types directly.
|
|
458
|
+
func (c *GoToTSCompiler) WriteFuncTypeAsArrow(exp *ast.FuncType) {
|
|
459
|
+
c.tsw.WriteLiterally("(")
|
|
460
|
+
c.WriteFieldList(exp.Params, true) // true = arguments
|
|
461
|
+
c.tsw.WriteLiterally(")")
|
|
462
|
+
c.tsw.WriteLiterally(" => ")
|
|
463
|
+
|
|
464
|
+
if exp.Results != nil && len(exp.Results.List) > 0 {
|
|
465
|
+
// Count total number of return values
|
|
466
|
+
totalResults := 0
|
|
467
|
+
for _, field := range exp.Results.List {
|
|
468
|
+
count := len(field.Names)
|
|
469
|
+
if count == 0 {
|
|
470
|
+
count = 1
|
|
471
|
+
}
|
|
472
|
+
totalResults += count
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if totalResults == 1 {
|
|
476
|
+
c.WriteTypeExpr(exp.Results.List[0].Type)
|
|
477
|
+
} else {
|
|
478
|
+
// Multiple return types -> tuple
|
|
479
|
+
c.tsw.WriteLiterally("[")
|
|
480
|
+
first := true
|
|
481
|
+
for _, field := range exp.Results.List {
|
|
482
|
+
count := len(field.Names)
|
|
483
|
+
if count == 0 {
|
|
484
|
+
count = 1
|
|
485
|
+
}
|
|
486
|
+
for j := 0; j < count; j++ {
|
|
487
|
+
if !first {
|
|
488
|
+
c.tsw.WriteLiterally(", ")
|
|
489
|
+
}
|
|
490
|
+
first = false
|
|
491
|
+
c.WriteTypeExpr(field.Type)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
c.tsw.WriteLiterally("]")
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
c.tsw.WriteLiterally("void")
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
450
501
|
// WriteInterfaceType translates a Go interface type to its TypeScript equivalent.
|
|
451
502
|
// It specially handles the error interface as $.GoError, and delegates to
|
|
452
503
|
// writeInterfaceStructure for other interface types, prepending "null | ".
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Package wasm provides a WASM-friendly API for compiling Go source code to TypeScript.
|
|
2
|
+
package wasm
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"github.com/aperturerobotics/goscript/compiler"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// CompileSource compiles Go source code to TypeScript.
|
|
9
|
+
// It takes the source code as a string and returns the generated TypeScript.
|
|
10
|
+
func CompileSource(source string, packageName string) (string, error) {
|
|
11
|
+
return compiler.CompileSourceToTypeScript(source, packageName)
|
|
12
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"fmt"
|
|
6
|
+
"go/ast"
|
|
7
|
+
"go/importer"
|
|
8
|
+
"go/parser"
|
|
9
|
+
"go/token"
|
|
10
|
+
"go/types"
|
|
11
|
+
"io"
|
|
12
|
+
|
|
13
|
+
"golang.org/x/tools/go/packages"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// CompileSourceToTypeScript compiles Go source code directly to TypeScript.
|
|
17
|
+
// This is a WASM-friendly API that bypasses filesystem operations.
|
|
18
|
+
// It takes the source code as a string and returns the generated TypeScript.
|
|
19
|
+
func CompileSourceToTypeScript(source string, packageName string) (string, error) {
|
|
20
|
+
if packageName == "" {
|
|
21
|
+
packageName = "main"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Create a new file set for position information
|
|
25
|
+
fset := token.NewFileSet()
|
|
26
|
+
|
|
27
|
+
// Parse the source code
|
|
28
|
+
astFile, err := parser.ParseFile(fset, "main.go", source, parser.ParseComments)
|
|
29
|
+
if err != nil {
|
|
30
|
+
return "", fmt.Errorf("parse error: %w", err)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Create type checker configuration with a custom importer
|
|
34
|
+
var typeErrors []error
|
|
35
|
+
conf := types.Config{
|
|
36
|
+
Importer: &wasmImporter{
|
|
37
|
+
defaultImporter: importer.Default(),
|
|
38
|
+
cache: make(map[string]*types.Package),
|
|
39
|
+
},
|
|
40
|
+
Error: func(err error) {
|
|
41
|
+
// Collect errors but don't fail immediately
|
|
42
|
+
typeErrors = append(typeErrors, err)
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Type check the package
|
|
47
|
+
info := &types.Info{
|
|
48
|
+
Types: make(map[ast.Expr]types.TypeAndValue),
|
|
49
|
+
Defs: make(map[*ast.Ident]types.Object),
|
|
50
|
+
Uses: make(map[*ast.Ident]types.Object),
|
|
51
|
+
Implicits: make(map[ast.Node]types.Object),
|
|
52
|
+
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
|
53
|
+
Scopes: make(map[ast.Node]*types.Scope),
|
|
54
|
+
Instances: make(map[*ast.Ident]types.Instance),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pkg, _ := conf.Check(packageName, fset, []*ast.File{astFile}, info)
|
|
58
|
+
// We continue even with type check errors for playground flexibility
|
|
59
|
+
|
|
60
|
+
// Create a packages.Package compatible structure
|
|
61
|
+
pkgData := &packages.Package{
|
|
62
|
+
ID: packageName,
|
|
63
|
+
Name: astFile.Name.Name,
|
|
64
|
+
PkgPath: packageName,
|
|
65
|
+
Fset: fset,
|
|
66
|
+
Syntax: []*ast.File{astFile},
|
|
67
|
+
Types: pkg,
|
|
68
|
+
TypesInfo: info,
|
|
69
|
+
CompiledGoFiles: []string{"main.go"},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Create an empty map for all packages (we only have one)
|
|
73
|
+
allPackages := map[string]*packages.Package{
|
|
74
|
+
packageName: pkgData,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Perform package-level analysis
|
|
78
|
+
packageAnalysis := AnalyzePackageImports(pkgData)
|
|
79
|
+
analysis := AnalyzePackageFiles(pkgData, allPackages)
|
|
80
|
+
|
|
81
|
+
// Create a buffer to capture the output
|
|
82
|
+
var buf bytes.Buffer
|
|
83
|
+
|
|
84
|
+
// Create the file compiler
|
|
85
|
+
fileCompiler := &FileCompiler{
|
|
86
|
+
compilerConfig: &Config{},
|
|
87
|
+
pkg: pkgData,
|
|
88
|
+
ast: astFile,
|
|
89
|
+
fullPath: "main.go",
|
|
90
|
+
Analysis: analysis,
|
|
91
|
+
PackageAnalysis: packageAnalysis,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Compile to the buffer
|
|
95
|
+
if err := fileCompiler.CompileToWriter(&buf); err != nil {
|
|
96
|
+
return "", fmt.Errorf("compilation error: %w", err)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return buf.String(), nil
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// CompileToWriter compiles the Go file and writes TypeScript to the given writer.
|
|
103
|
+
// This is used for WASM compilation where we don't want file I/O.
|
|
104
|
+
func (c *FileCompiler) CompileToWriter(w io.Writer) error {
|
|
105
|
+
f := c.ast
|
|
106
|
+
|
|
107
|
+
c.codeWriter = NewTSCodeWriter(w)
|
|
108
|
+
|
|
109
|
+
// Pass analysis to compiler
|
|
110
|
+
goWriter := NewGoToTSCompiler(c.codeWriter, c.pkg, c.Analysis, c.fullPath)
|
|
111
|
+
|
|
112
|
+
// Add import for the goscript runtime using namespace import and alias
|
|
113
|
+
c.codeWriter.WriteLinef("import * as $ from %q", "@goscript/builtin/index.js")
|
|
114
|
+
|
|
115
|
+
c.codeWriter.WriteLine("") // Add a newline after imports
|
|
116
|
+
|
|
117
|
+
if err := goWriter.WriteDecls(f.Decls); err != nil {
|
|
118
|
+
return fmt.Errorf("failed to write declarations: %w", err)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return nil
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// wasmImporter provides import resolution for WASM compilation.
|
|
125
|
+
// It wraps the default importer and provides stubs for common packages.
|
|
126
|
+
type wasmImporter struct {
|
|
127
|
+
defaultImporter types.Importer
|
|
128
|
+
cache map[string]*types.Package
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func (i *wasmImporter) Import(path string) (*types.Package, error) {
|
|
132
|
+
if pkg, ok := i.cache[path]; ok {
|
|
133
|
+
return pkg, nil
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Try the default importer first (works for stdlib packages)
|
|
137
|
+
pkg, err := i.defaultImporter.Import(path)
|
|
138
|
+
if err == nil {
|
|
139
|
+
i.cache[path] = pkg
|
|
140
|
+
return pkg, nil
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// For unknown packages, create an empty stub package
|
|
144
|
+
// This allows compilation to proceed even if dependencies aren't available
|
|
145
|
+
stubPkg := types.NewPackage(path, pathToName(path))
|
|
146
|
+
stubPkg.MarkComplete()
|
|
147
|
+
i.cache[path] = stubPkg
|
|
148
|
+
return stubPkg, nil
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// pathToName extracts a package name from an import path
|
|
152
|
+
func pathToName(path string) string {
|
|
153
|
+
// Find the last component of the path
|
|
154
|
+
for i := len(path) - 1; i >= 0; i-- {
|
|
155
|
+
if path[i] == '/' {
|
|
156
|
+
return path[i+1:]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return path
|
|
160
|
+
}
|