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.
@@ -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
+ }
@@ -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: 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),
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
 
@@ -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, patterns...)
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
- if _, isMapType := exp.Type.(*ast.MapType); isMapType {
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
- return fmt.Errorf("unhandled string conversion: %s", exp.Fun)
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 {
@@ -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 funcType, ok := c.pkg.TypesInfo.TypeOf(callExpr.Fun).Underlying().(*types.Signature); ok {
314
- if funcType.Results() != nil && funcType.Results().Len() > 0 {
315
- for i := 0; i < funcType.Results().Len(); i++ {
316
- resultTypes = append(resultTypes, funcType.Results().At(i))
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
+ }