diff --git a/internal/implementations/extractor.go b/internal/implementations/extractor.go new file mode 100644 index 0000000..a8b0a40 --- /dev/null +++ b/internal/implementations/extractor.go @@ -0,0 +1,150 @@ +package impls + +import ( + "fmt" + "go/types" + "log/slog" + + "github.com/scip-code/scip-go/internal/loader" + "github.com/scip-code/scip-go/internal/lookup" + "github.com/scip-code/scip/bindings/go/scip" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/typeutil" +) + +type Extractor struct { + global *lookup.Global + methodSetCache typeutil.MethodSetCache +} + +func NewExtractor(global *lookup.Global) *Extractor { + return &Extractor{ + global: global, + } +} + +func (e *Extractor) Extract(pkgLookup loader.PackageLookup) (map[string]ImplDef, map[string]ImplDef) { + interfaces := map[string]ImplDef{} + concretes := map[string]ImplDef{} + + for _, pkg := range pkgLookup { + if pkg.Name == "builtin" { + continue + } + + if pkg.TypesInfo != nil { + e.extractLocal(pkg, interfaces, concretes) + } else if pkg.Types != nil { + e.extractRemote(pkg, interfaces, concretes) + } else { + slog.Warn("No types for package", "path", pkg.PkgPath) + } + } + + return interfaces, concretes +} + +func (e *Extractor) extractLocal(pkg *packages.Package, interfaces, concretes map[string]ImplDef) { + pkgSymbols := e.global.GetPackage(pkg) + if pkgSymbols == nil { + slog.Warn("No symbols for package", "path", pkg.PkgPath) + return + } + + for ident, obj := range pkg.TypesInfo.Defs { + if obj == nil { + continue + } + + typeName, ok := obj.(*types.TypeName) + if !ok { + continue + } + + if pkg.Types != nil && typeName.Parent() != pkg.Types.Scope() { + continue + } + + named, ok := obj.Type().(*types.Named) + if !ok { + continue + } + + sym, ok := pkgSymbols.Get(typeName.Pos()) + if !ok { + slog.Debug( + "No symbol for package-level named type", + "identifier", ident.Name, + "package", pkg.PkgPath, + "id", obj.Id(), + ) + continue + } + + e.classify(named, sym, pkg.PkgPath, interfaces, concretes) + } +} + +func (e *Extractor) extractRemote(pkg *packages.Package, interfaces, concretes map[string]ImplDef) { + scope := pkg.Types.Scope() + + for _, name := range scope.Names() { + typeName, ok := scope.Lookup(name).(*types.TypeName) + if !ok || !typeName.Exported() { + continue + } + + named, ok := typeName.Type().(*types.Named) + if !ok { + continue + } + + sym := e.global.Composer().Compose(pkg, typeName) + if sym == "" { + continue + } + + e.classify(named, &scip.SymbolInformation{Symbol: sym}, pkg.PkgPath, interfaces, concretes) + } +} + +func (e *Extractor) classify( + named *types.Named, + sym *scip.SymbolInformation, + pkgPath string, + interfaces, concretes map[string]ImplDef, +) { + methods := typeutil.IntuitiveMethodSet(named, &e.methodSetCache) + if len(methods) == 0 { + return + } + + methodSymbols := map[methodID]*scip.SymbolInformation{} + for _, method := range methods { + sym, ok, err := e.global.GetSymbolOfObject(method.Obj()) + if err != nil { + slog.Debug(fmt.Sprintf("Error while looking for symbol %s | %s", err, method.Obj())) + continue + } + if !ok { + continue + } + + methodSymbols[methodID(method.Obj().Id())] = sym + } + + impl := ImplDef{ + Symbol: sym, + Named: named, + Methods: methodSymbols, + Mask: methodMask(methods), + MethodCount: len(methods), + HasUnexported: hasUnexportedMethods(methods), + PkgPath: pkgPath, + } + if types.IsInterface(named) { + interfaces[impl.Symbol.Symbol] = impl + } else { + concretes[impl.Symbol.Symbol] = impl + } +} diff --git a/internal/implementations/implementations.go b/internal/implementations/implementations.go index ef3d586..107a82b 100644 --- a/internal/implementations/implementations.go +++ b/internal/implementations/implementations.go @@ -1,21 +1,15 @@ package impls import ( - "fmt" - "go/ast" "go/types" "hash/crc32" - "log/slog" "sync" "sync/atomic" "github.com/scip-code/scip-go/internal/implementations/fingerprint" "github.com/scip-code/scip-go/internal/loader" - "github.com/scip-code/scip-go/internal/lookup" "github.com/scip-code/scip-go/internal/output" "github.com/scip-code/scip/bindings/go/scip" - "golang.org/x/tools/go/packages" - "golang.org/x/tools/go/types/typeutil" ) // methodID is a unique identifier for a method, using types.Id semantics @@ -23,11 +17,7 @@ import ( type methodID string type ImplDef struct { - // The corresponding scip symbol, generated via previous iteration over the AST - Symbol *scip.SymbolInformation - - Pkg *packages.Package - Ident *ast.Ident + Symbol *scip.SymbolInformation Named *types.Named Methods map[methodID]*scip.SymbolInformation @@ -69,19 +59,9 @@ func hasUnexportedMethods(methods []*types.Selection) bool { return false } -func findImplementations(concreteTypes map[string]ImplDef, interfaces map[string]ImplDef, symbols *lookup.Global, count *uint64) { +func findImplementations(concreteTypes map[string]ImplDef, interfaces map[string]ImplDef, count *uint64) { for _, ty := range concreteTypes { - pos := ty.Ident.Pos() - sym, ok := symbols.GetSymbolInformation(ty.Pkg, pos) - if !ok { - panic(fmt.Sprintf("Could not find symbol for %s", ty.Symbol)) - } - for _, iface := range interfaces { - if iface.Ident == nil { - continue - } - ifaceType, ok := iface.Named.Underlying().(*types.Interface) if !ok { continue @@ -114,7 +94,7 @@ func findImplementations(concreteTypes map[string]ImplDef, interfaces map[string } // Add implementation details for the struct & interface relationship - sym.Relationships = append(sym.Relationships, &scip.Relationship{ + ty.Symbol.Relationships = append(ty.Symbol.Relationships, &scip.Relationship{ Symbol: iface.Symbol.Symbol, IsImplementation: true, }) @@ -133,6 +113,11 @@ func findImplementations(concreteTypes map[string]ImplDef, interfaces map[string } } + ty.Symbol.Relationships = scip.CanonicalizeRelationships(ty.Symbol.Relationships) + for _, method := range ty.Methods { + method.Relationships = scip.CanonicalizeRelationships(method.Relationships) + } + atomic.AddUint64(count, 1) } } @@ -140,16 +125,11 @@ func findImplementations(concreteTypes map[string]ImplDef, interfaces map[string func AddImplementationRelationships( pkgs loader.PackageLookup, allPackages loader.PackageLookup, - symbols *lookup.Global, + extractor *Extractor, ) ([]*scip.SymbolInformation, error) { var externalSymbols []*scip.SymbolInformation - var msCache typeutil.MethodSetCache - localInterfaces, localTypes, err := extractInterfacesAndConcreteTypes( - pkgs, symbols, &msCache) - if err != nil { - return nil, err - } + localInterfaces, localTypes := extractor.Extract(pkgs) remotePackages := make(loader.PackageLookup) for pkgID, pkg := range allPackages { @@ -159,11 +139,7 @@ func AddImplementationRelationships( remotePackages[pkgID] = pkg } - remoteInterfaces, remoteTypes, err := extractInterfacesAndConcreteTypes( - remotePackages, symbols, &msCache) - if err != nil { - return nil, err - } + remoteInterfaces, remoteTypes := extractor.Extract(remotePackages) // Total concrete types to check across the three passes. total := uint64(len(localTypes)*2 + len(remoteTypes)) @@ -175,131 +151,24 @@ func AddImplementationRelationships( defer wg.Done() // local type -> local interface - findImplementations(localTypes, localInterfaces, symbols, &count) + findImplementations(localTypes, localInterfaces, &count) // local type -> remote interface - findImplementations(localTypes, remoteInterfaces, symbols, &count) + findImplementations(localTypes, remoteInterfaces, &count) // remote type -> local interface // We emit these as external symbols so index consumer can merge them. - findImplementations(remoteTypes, localInterfaces, symbols, &count) + findImplementations(remoteTypes, localInterfaces, &count) }() output.WithProgressParallel(&wg, "Indexing Implementations", &count, total) // Collect remote type symbols that gained relationships for _, typ := range remoteTypes { - if sym, ok := symbols.GetSymbolInformation(typ.Pkg, typ.Ident.Pos()); ok { - if len(sym.Relationships) > 0 { - externalSymbols = append(externalSymbols, sym) - } + if len(typ.Symbol.Relationships) > 0 { + externalSymbols = append(externalSymbols, typ.Symbol) } } return externalSymbols, nil } - -func extractInterfacesAndConcreteTypes( - pkgs loader.PackageLookup, - symbols *lookup.Global, - msCache *typeutil.MethodSetCache, -) (interfaces map[string]ImplDef, concreteTypes map[string]ImplDef, err error) { - interfaces = map[string]ImplDef{} - concreteTypes = map[string]ImplDef{} - - for _, pkg := range pkgs { - // Builtin isn't the same as standard library, that is for builtin types - // We don't need to check those for implemenations. - if pkg.Name == "builtin" { - continue - } - - if pkg.TypesInfo == nil { - slog.Warn("No types for package", "path", pkg.PkgPath) - continue - } - - pkgSymbols := symbols.GetPackage(pkg) - if pkgSymbols == nil { - slog.Warn("No symbols for package", "path", pkg.PkgPath) - continue - } - - for ident, obj := range pkg.TypesInfo.Defs { - if obj == nil { - continue - } - - // We ignore aliases 'type M = N' to avoid duplicate reporting - // of the Named type N. - obj, ok := obj.(*types.TypeName) - if !ok { - continue - } - - // Skip types declared inside function bodies — the type visitor - // only indexes package-level declarations, so local types will - // never have a symbol entry. - if pkg.Types != nil && obj.Parent() != pkg.Types.Scope() { - continue - } - - objType, ok := obj.Type().(*types.Named) - if !ok { - continue - } - - symbol, ok := pkgSymbols.Get(obj.Pos()) - if !ok { - slog.Debug( - "No symbol for package-level named type", - "identifier", ident.Name, "package", pkg.PkgPath, "id", obj.Id()) - continue - } - - methods := typeutil.IntuitiveMethodSet(objType, msCache) - - // ignore interfaces that are empty. they are too - // plentiful and don't provide useful intelligence. - if len(methods) == 0 { - continue - } - - methodIds := map[methodID]*scip.SymbolInformation{} - for _, m := range methods { - sym, ok, err := symbols.GetSymbolOfObject(m.Obj()) - if err != nil { - slog.Debug(fmt.Sprintf("Error while looking for symbol %s | %s", err, m.Obj())) - continue - } - - if !ok { - continue - } - - methodIds[methodID(m.Obj().Id())] = sym - } - - d := ImplDef{ - Symbol: symbol, - Pkg: pkg, - Ident: ident, - Named: objType, - Methods: methodIds, - Mask: methodMask(methods), - MethodCount: len(methods), - HasUnexported: hasUnexportedMethods(methods), - PkgPath: pkg.PkgPath, - } - - if types.IsInterface(objType) { - interfaces[d.Symbol.Symbol] = d - } else { - concreteTypes[d.Symbol.Symbol] = d - } - - } - } - - return -} diff --git a/internal/index/scip.go b/internal/index/scip.go index 3154dda..5c7b27c 100644 --- a/internal/index/scip.go +++ b/internal/index/scip.go @@ -55,15 +55,18 @@ func GetPackages(opts config.IndexOpts) (current []newtypes.PackageID, deps []ne } func ListMissing(opts config.IndexOpts) (missing []string, err error) { - projectPackages, allPackages, err := loader.LoadPackages(opts, opts.ModuleRoot) + projectPackages, _, err := loader.LoadPackages(opts, opts.ModuleRoot) if err != nil { return nil, err } + composer := symbols.NewComposer(opts.ModuleRoot, opts.ModuleVersion) + globalSymbols := lookup.NewGlobalSymbols(composer) + pathToDocuments := map[string]*document.Document{} - for _, pkg := range allPackages { + for _, pkg := range projectPackages { visitors.VisitPackageSyntax( - opts.ModuleRoot, pkg, pathToDocuments, lookup.NewGlobalSymbols()) + opts.ModuleRoot, pkg, pathToDocuments, globalSymbols) } for _, pkg := range projectPackages { @@ -104,7 +107,7 @@ func Index(writer func(proto.Message) error, opts config.IndexOpts) error { pathToDocument, globalSymbols := indexVisitPackages(opts, projectPackages, allPackages) if !opts.SkipImplementations { implSymbols, err := impls.AddImplementationRelationships( - projectPackages, allPackages, globalSymbols, + projectPackages, allPackages, impls.NewExtractor(globalSymbols), ) if err != nil { return err @@ -180,39 +183,30 @@ func indexVisitPackages( allPackages loader.PackageLookup, ) (map[string]*document.Document, *lookup.Global) { pathToDocuments := map[string]*document.Document{} - globalSymbols := lookup.NewGlobalSymbols() + + composer := symbols.NewComposer(opts.ModuleRoot, opts.ModuleVersion) + globalSymbols := lookup.NewGlobalSymbols(composer) + for _, pkg := range allPackages { + globalSymbols.SetPkgSymbol(pkg) + } var count uint64 var wg sync.WaitGroup wg.Add(1) - lookupIDs := slices.Sorted(maps.Keys(allPackages)) + lookupIDs := slices.Sorted(maps.Keys(projectPackages)) - // We have to visit all the packages to get the definition sites - // for all the symbols. - // - // We don't want to visit in the same depth as file visitors though, - // so we do ONLY do this + // Visit project packages to collect definition sites. Dependency packages + // skip syntax visiting; their symbols are composed on demand. go func() { defer wg.Done() for _, pkgID := range lookupIDs { - pkg := allPackages[pkgID] + pkg := projectPackages[pkgID] slog.Debug("Visiting package", "path", pkg.PkgPath) visitors.VisitPackageSyntax(opts.ModuleRoot, pkg, pathToDocuments, globalSymbols) - pkgSymbol := globalSymbols.SetPkgSymbol(pkg) - - // If we don't have this package anywhere, don't try to create a new symbol - if _, ok := projectPackages[newtypes.GetID(pkg)]; !ok { - atomic.AddUint64(&count, 1) - continue - } - - if len(pkg.Syntax) == 0 { - atomic.AddUint64(&count, 1) - continue - } + pkgSymbol, _ := globalSymbols.GetPkgSymbol(pkg) symInfo := &scip.SymbolInformation{ Symbol: pkgSymbol, diff --git a/internal/loader/loader.go b/internal/loader/loader.go index 46f5e36..087f2e1 100644 --- a/internal/loader/loader.go +++ b/internal/loader/loader.go @@ -19,7 +19,7 @@ import ( type PackageLookup map[newtypes.PackageID]*packages.Package -var loadMode = packages.NeedDeps | +var loadMode = packages.NeedExportFile | packages.NeedImports | packages.NeedSyntax | packages.NeedTypes | @@ -119,7 +119,7 @@ func LoadPackages( for _, pkg := range pkgs { addImportsToPkgs(allPackages, &opts, pkg) - projectPackages[newtypes.GetID(pkg)] = pkg + projectPackages[newtypes.GetID(pkg)] = allPackages[newtypes.GetID(pkg)] } return nil diff --git a/internal/lookup/lookup.go b/internal/lookup/lookup.go index 4408778..51c9b68 100644 --- a/internal/lookup/lookup.go +++ b/internal/lookup/lookup.go @@ -21,10 +21,11 @@ func NewPackageSymbols(pkg *packages.Package) *Package { } } -func NewGlobalSymbols() *Global { +func NewGlobalSymbols(composer *symbols.Composer) *Global { return &Global{ symbols: map[newtypes.PackageID]*Package{}, pkgSymbols: map[newtypes.PackageID]string{}, + composer: composer, } } @@ -75,6 +76,11 @@ type Global struct { m sync.Mutex symbols map[newtypes.PackageID]*Package pkgSymbols map[newtypes.PackageID]string + composer *symbols.Composer +} + +func (p *Global) Composer() *symbols.Composer { + return p.composer } func (p *Global) Add(pkgSymbols *Package) { @@ -90,6 +96,7 @@ func (p *Global) SetPkgSymbol(pkg *packages.Package) string { }) p.m.Lock() p.pkgSymbols[newtypes.GetID(pkg)] = sym + p.symbols[newtypes.GetID(pkg)] = NewPackageSymbols(pkg) p.m.Unlock() return sym } @@ -171,6 +178,12 @@ func (p *Global) GetSymbolOfObject(obj types.Object) (*scip.SymbolInformation, b } } + if pkgSymbols, ok := p.symbols[newtypes.PackageID(pkgPath)]; ok { + if sym := p.composer.Compose(pkgSymbols.pkg, obj); sym != "" { + return &scip.SymbolInformation{Symbol: sym}, true, nil + } + } + switch obj := obj.(type) { case *types.Var: // , "| position", pkg.Fset.Position(obj.Pos()))) diff --git a/internal/symbols/composer.go b/internal/symbols/composer.go new file mode 100644 index 0000000..6bb373d --- /dev/null +++ b/internal/symbols/composer.go @@ -0,0 +1,237 @@ +package symbols + +import ( + "go/types" + "sync" + + "github.com/scip-code/scip/bindings/go/scip" + "golang.org/x/tools/go/packages" +) + +type Composer struct { + defaultModulePath string + defaultModuleVersion string + + mu sync.Mutex + compositions map[types.Object]string +} + +func NewComposer(defaultModulePath, defaultModuleVersion string) *Composer { + return &Composer{ + defaultModulePath: defaultModulePath, + defaultModuleVersion: defaultModuleVersion, + + compositions: make(map[types.Object]string), + } +} + +func (c *Composer) Compose(pkg *packages.Package, obj types.Object) string { + if obj == nil || obj.Pkg() == nil { + return "" + } + + switch o := obj.(type) { + case *types.Var: + obj = o.Origin() + case *types.Func: + obj = o.Origin() + } + + c.mu.Lock() + defer c.mu.Unlock() + + if composition, ok := c.compositions[obj]; ok { + return composition + } + + var descriptors []*scip.Descriptor + switch obj := obj.(type) { + case *types.PkgName: + descriptors = c.describePkgName(obj) + case *types.TypeName: + descriptors = c.describeTypeName(obj) + case *types.Const: + descriptors = c.describeConst(obj) + case *types.Func: + descriptors = c.describeFunc(obj) + case *types.Var: + descriptors = c.describeVar(obj) + } + if len(descriptors) == 0 { + return "" + } + + c.compositions[obj] = scip.VerboseSymbolFormatter.FormatSymbol(&scip.Symbol{ + Scheme: "scip-go", + Package: c.pack(pkg), + Descriptors: descriptors, + }) + return c.compositions[obj] +} + +func (c *Composer) pack(pkg *packages.Package) *scip.Package { + if pkg == nil || pkg.Module == nil { + return &scip.Package{ + Manager: "gomod", + Name: c.defaultModulePath, + Version: c.defaultModuleVersion, + } + } + + return &scip.Package{ + Manager: "gomod", + Name: pkg.Module.Path, + Version: pkg.Module.Version, + } +} + +func (c *Composer) describePkgName(pkgName *types.PkgName) []*scip.Descriptor { + return []*scip.Descriptor{ + { + Name: pkgName.Imported().Path(), + Suffix: scip.Descriptor_Namespace, + }, + } +} + +func (c *Composer) describeTypeName(typeName *types.TypeName) []*scip.Descriptor { + return []*scip.Descriptor{ + { + Name: typeName.Pkg().Path(), + Suffix: scip.Descriptor_Namespace, + }, + { + Name: typeName.Name(), + Suffix: scip.Descriptor_Type, + }, + } +} + +func (c *Composer) describeConst(constant *types.Const) []*scip.Descriptor { + return []*scip.Descriptor{ + { + Name: constant.Pkg().Path(), + Suffix: scip.Descriptor_Namespace, + }, + { + Name: constant.Name(), + Suffix: scip.Descriptor_Term, + }, + } +} + +func (c *Composer) describeFunc(fn *types.Func) []*scip.Descriptor { + sig, ok := fn.Type().(*types.Signature) + if !ok { + return nil + } + + if recv := sig.Recv(); recv != nil { + recvTypeName := c.nameType(recv.Type()) + if recvTypeName == "" { + return nil + } + + return []*scip.Descriptor{ + { + Name: fn.Pkg().Path(), + Suffix: scip.Descriptor_Namespace, + }, + { + Name: recvTypeName, + Suffix: scip.Descriptor_Type, + }, + { + Name: fn.Name(), + Suffix: scip.Descriptor_Method, + }, + } + } + + return []*scip.Descriptor{ + { + Name: fn.Pkg().Path(), + Suffix: scip.Descriptor_Namespace, + }, + { + Name: fn.Name(), + Suffix: scip.Descriptor_Method, + }, + } +} + +func (c *Composer) nameType(t types.Type) string { + for { + switch u := types.Unalias(t).(type) { + case *types.Pointer: + t = u.Elem() + case *types.Named: + return u.Obj().Name() + default: + return "" + } + } +} + +func (c *Composer) describeVar(variable *types.Var) []*scip.Descriptor { + if !variable.IsField() { + return []*scip.Descriptor{ + { + Name: variable.Pkg().Path(), + Suffix: scip.Descriptor_Namespace, + }, + { + Name: variable.Name(), + Suffix: scip.Descriptor_Term, + }, + } + } + + owner := c.locateOwner(variable) + if owner == nil { + return nil + } + + return []*scip.Descriptor{ + { + Name: variable.Pkg().Path(), + Suffix: scip.Descriptor_Namespace, + }, + { + Name: owner.Name(), + Suffix: scip.Descriptor_Type, + }, + { + Name: variable.Name(), + Suffix: scip.Descriptor_Term, + }, + } +} + +func (c *Composer) locateOwner(field *types.Var) *types.TypeName { + scope := field.Pkg().Scope() + for _, name := range scope.Names() { + typeName, ok := scope.Lookup(name).(*types.TypeName) + if !ok { + continue + } + + named, ok := typeName.Type().(*types.Named) + if !ok { + continue + } + + structType, ok := named.Underlying().(*types.Struct) + if !ok { + continue + } + + for f := range structType.Fields() { + if f == field { + return typeName + } + } + } + + return nil +} diff --git a/internal/symbols/composer_test.go b/internal/symbols/composer_test.go new file mode 100644 index 0000000..8e6d5f0 --- /dev/null +++ b/internal/symbols/composer_test.go @@ -0,0 +1,172 @@ +package symbols_test + +import ( + "go/token" + "go/types" + "testing" + + "github.com/scip-code/scip-go/internal/symbols" + "golang.org/x/tools/go/packages" +) + +func TestComposeNil(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + if got := c.Compose(pkg, nil); got != "" { + t.Errorf("Compose(nil) = %q, want empty", got) + } +} + +func TestComposeNilPkg(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + obj := types.Universe.Lookup("len") + if got := c.Compose(pkg, obj); got != "" { + t.Errorf("Compose(builtin) = %q, want empty", got) + } +} + +func TestComposeTypeName(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + obj := types.NewTypeName(token.NoPos, tpkg, "MyStruct", nil) + types.NewNamed(obj, types.NewStruct(nil, nil), nil) + + got := c.Compose(pkg, obj) + want := "scip-go gomod example.com/lib v1.0.0 `example.com/lib`/MyStruct#" + if got != want { + t.Errorf("Compose(TypeName) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposeConst(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + obj := types.NewConst(token.NoPos, tpkg, "MaxRetries", types.Typ[types.Int], nil) + + got := c.Compose(pkg, obj) + want := "scip-go gomod example.com/lib v1.0.0 `example.com/lib`/MaxRetries." + if got != want { + t.Errorf("Compose(Const) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposePackageVar(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + obj := types.NewVar(token.NoPos, tpkg, "GlobalCount", types.Typ[types.Int]) + + got := c.Compose(pkg, obj) + want := "scip-go gomod example.com/lib v1.0.0 `example.com/lib`/GlobalCount." + if got != want { + t.Errorf("Compose(Var) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposeFunc(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) + obj := types.NewFunc(token.NoPos, tpkg, "DoWork", sig) + + got := c.Compose(pkg, obj) + want := "scip-go gomod example.com/lib v1.0.0 `example.com/lib`/DoWork()." + if got != want { + t.Errorf("Compose(Func) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposeMethod(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + typeName := types.NewTypeName(token.NoPos, tpkg, "Server", nil) + named := types.NewNamed(typeName, types.NewStruct(nil, nil), nil) + + recv := types.NewVar(token.NoPos, tpkg, "s", named) + sig := types.NewSignatureType(recv, nil, nil, nil, nil, false) + obj := types.NewFunc(token.NoPos, tpkg, "Start", sig) + + got := c.Compose(pkg, obj) + want := "scip-go gomod example.com/lib v1.0.0 `example.com/lib`/Server#Start()." + if got != want { + t.Errorf("Compose(Method) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposeMethodPointerReceiver(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + typeName := types.NewTypeName(token.NoPos, tpkg, "Server", nil) + named := types.NewNamed(typeName, types.NewStruct(nil, nil), nil) + + recv := types.NewVar(token.NoPos, tpkg, "s", types.NewPointer(named)) + sig := types.NewSignatureType(recv, nil, nil, nil, nil, false) + obj := types.NewFunc(token.NoPos, tpkg, "Stop", sig) + + got := c.Compose(pkg, obj) + want := "scip-go gomod example.com/lib v1.0.0 `example.com/lib`/Server#Stop()." + if got != want { + t.Errorf("Compose(Method ptr recv) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposeStructField(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + field := types.NewField(token.NoPos, tpkg, "Name", types.Typ[types.String], false) + structType := types.NewStruct([]*types.Var{field}, nil) + + typeName := types.NewTypeName(token.NoPos, tpkg, "Config", nil) + types.NewNamed(typeName, structType, nil) + tpkg.Scope().Insert(typeName) + + got := c.Compose(pkg, field) + want := "scip-go gomod example.com/lib v1.0.0 `example.com/lib`/Config#Name." + if got != want { + t.Errorf("Compose(Field) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposeDefaultModule(t *testing.T) { + c := symbols.NewComposer("example.com/project", "1.0.0") + pkg := &packages.Package{PkgPath: "example.com/lib"} + + tpkg := types.NewPackage("example.com/lib", "lib") + obj := types.NewConst(token.NoPos, tpkg, "Version", types.Typ[types.String], nil) + + got := c.Compose(pkg, obj) + want := "scip-go gomod example.com/project 1.0.0 `example.com/lib`/Version." + if got != want { + t.Errorf("Compose(default module) =\n %q\nwant\n %q", got, want) + } +} + +func TestComposeMemoization(t *testing.T) { + c := symbols.NewComposer("", "") + pkg := &packages.Package{PkgPath: "example.com/lib", Module: &packages.Module{Path: "example.com/lib", Version: "v1.0.0"}} + + tpkg := types.NewPackage("example.com/lib", "lib") + obj := types.NewConst(token.NoPos, tpkg, "X", types.Typ[types.Int], nil) + + first := c.Compose(pkg, obj) + second := c.Compose(pkg, obj) + if first != second { + t.Errorf("memoization broken: %q != %q", first, second) + } +} diff --git a/internal/testdata/snapshots/input/pr222/README.md b/internal/testdata/snapshots/input/pr222/README.md new file mode 100644 index 0000000..739a1d1 --- /dev/null +++ b/internal/testdata/snapshots/input/pr222/README.md @@ -0,0 +1,13 @@ +# On-demand dependency symbol resolution + +Tests that references into a dependency module produce correct symbols when the +dependency's symbols are resolved on demand by the `Composer` rather than +eagerly indexed. + +Exercises the major symbol shapes a dependency can expose: generic and +non-generic struct fields, methods on generic types, package-level consts and +vars, embedded fields, and interface methods used through a dependency-defined +interface. + +The `deplib/` subdirectory is a separate module wired in via a `replace` +directive in `go.mod`. diff --git a/internal/testdata/snapshots/input/pr222/deplib/box.go b/internal/testdata/snapshots/input/pr222/deplib/box.go new file mode 100644 index 0000000..dfd92cf --- /dev/null +++ b/internal/testdata/snapshots/input/pr222/deplib/box.go @@ -0,0 +1,26 @@ +package deplib + +type Box[T any] struct { + Value T +} + +func (b Box[T]) Get() T { + return b.Value +} + +type Config struct { + Name string + Verbose bool +} + +const DefaultName = "default" + +var GlobalCounter int + +type Stringer interface { + String() string +} + +type Writer interface { + Write(p []byte) (n int, err error) +} diff --git a/internal/testdata/snapshots/input/pr222/deplib/go.mod b/internal/testdata/snapshots/input/pr222/deplib/go.mod new file mode 100644 index 0000000..ceea55a --- /dev/null +++ b/internal/testdata/snapshots/input/pr222/deplib/go.mod @@ -0,0 +1,3 @@ +module github.com/example/deplib + +go 1.22 diff --git a/internal/testdata/snapshots/input/pr222/go.mod b/internal/testdata/snapshots/input/pr222/go.mod new file mode 100644 index 0000000..1f173cd --- /dev/null +++ b/internal/testdata/snapshots/input/pr222/go.mod @@ -0,0 +1,7 @@ +module sg/pr222 + +go 1.22 + +require github.com/example/deplib v0.0.0 + +replace github.com/example/deplib => ./deplib diff --git a/internal/testdata/snapshots/input/pr222/pr222.go b/internal/testdata/snapshots/input/pr222/pr222.go new file mode 100644 index 0000000..e430843 --- /dev/null +++ b/internal/testdata/snapshots/input/pr222/pr222.go @@ -0,0 +1,42 @@ +package pr222 + +import "github.com/example/deplib" + +func UseGenericField() int { + b := deplib.Box[int]{Value: 42} + return b.Value +} + +func UseGenericMethod() string { + b := deplib.Box[string]{Value: "hello"} + return b.Get() +} + +func UseNonGenericField() string { + c := deplib.Config{Name: "test", Verbose: true} + return c.Name +} + +func UseConst() string { + return deplib.DefaultName +} + +func UseVar() int { + return deplib.GlobalCounter +} + +type LocalType struct{} + +func (l LocalType) String() string { return "local" } + +type EmbeddedStringer struct { + LocalType +} + +type LocalInterface interface { + Get() int +} + +func UseDepWriter(w deplib.Writer) { + w.Write(nil) +} diff --git a/internal/testdata/snapshots/output/embedded/embedded.go b/internal/testdata/snapshots/output/embedded/embedded.go index ec28e85..6fa1991 100755 --- a/internal/testdata/snapshots/output/embedded/embedded.go +++ b/internal/testdata/snapshots/output/embedded/embedded.go @@ -18,9 +18,7 @@ // display_name osExecCommand // signature_documentation // > type osExecCommand struct{ *exec.Cmd } -// relationship github.com/golang/go/src go1.22 context/stringer# implementation // relationship github.com/golang/go/src go1.22 fmt/Stringer# implementation -// relationship github.com/golang/go/src go1.22 runtime/stringer# implementation *exec.Cmd // ^^^^ reference github.com/golang/go/src go1.22 `os/exec`/ // ^^^ definition 0.1.test `sg/embedded`/osExecCommand#Cmd. diff --git a/internal/testdata/snapshots/output/embedded/nested.go b/internal/testdata/snapshots/output/embedded/nested.go index c869d73..bafcb47 100755 --- a/internal/testdata/snapshots/output/embedded/nested.go +++ b/internal/testdata/snapshots/output/embedded/nested.go @@ -50,10 +50,10 @@ _ = n.Handler.ServeHTTP // ^ reference local 0 // ^^^^^^^ reference 0.1.test `sg/embedded`/NestedHandler#Handler. -// ^^^^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/Handler#ServeHTTP. +// ^^^^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/Handler#ServeHTTP(). _ = n.ServeHTTP // ^ reference local 0 -// ^^^^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/Handler#ServeHTTP. +// ^^^^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/Handler#ServeHTTP(). _ = n.Other // ^ reference local 0 // ^^^^^ reference 0.1.test `sg/embedded`/NestedHandler#Other. diff --git a/internal/testdata/snapshots/output/embedded/something.go b/internal/testdata/snapshots/output/embedded/something.go index e78dedb..cdceb7b 100755 --- a/internal/testdata/snapshots/output/embedded/something.go +++ b/internal/testdata/snapshots/output/embedded/something.go @@ -17,9 +17,7 @@ // display_name String // signature_documentation // > func (*RecentCommittersResults).String() string -// relationship github.com/golang/go/src go1.22 context/stringer#String. implementation -// relationship github.com/golang/go/src go1.22 fmt/Stringer#String. implementation -// relationship github.com/golang/go/src go1.22 runtime/stringer#String. implementation +// relationship github.com/golang/go/src go1.22 fmt/Stringer#String(). implementation return fmt.Sprintf("RecentCommittersResults{Nodes: %d}", len(r.Nodes)) // ^^^ reference github.com/golang/go/src go1.22 fmt/ // ^^^^^^^ reference github.com/golang/go/src go1.22 fmt/Sprintf(). @@ -47,9 +45,7 @@ // > } // > PageInfo struct{ HasNextPage bool } // > } -// relationship github.com/golang/go/src go1.22 context/stringer# implementation // relationship github.com/golang/go/src go1.22 fmt/Stringer# implementation -// relationship github.com/golang/go/src go1.22 runtime/stringer# implementation Nodes []struct { // ^^^^^ definition 0.1.test `sg/embedded`/RecentCommittersResults#Nodes. // kind Field diff --git a/internal/testdata/snapshots/output/impls/remote_impls.go b/internal/testdata/snapshots/output/impls/remote_impls.go index 6447e46..e7d26e5 100755 --- a/internal/testdata/snapshots/output/impls/remote_impls.go +++ b/internal/testdata/snapshots/output/impls/remote_impls.go @@ -26,8 +26,6 @@ // display_name MyWriter // signature_documentation // > type MyWriter struct{} -// relationship github.com/golang/go/src go1.22 `crypto/tls`/transcriptHash# implementation -// relationship github.com/golang/go/src go1.22 `internal/bisect`/Writer# implementation // relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter# implementation // relationship github.com/golang/go/src go1.22 io/Writer# implementation @@ -44,7 +42,7 @@ // display_name Header // signature_documentation // > func (MyWriter).Header() http.Header -// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Header. implementation +// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Header(). implementation // ^^^^ reference github.com/golang/go/src go1.22 `net/http`/ // ^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/Header# // ⌃ enclosing_range_end 0.1.test `sg/impls`/MyWriter#Header(). @@ -61,10 +59,8 @@ // display_name Write // signature_documentation // > func (MyWriter).Write([]byte) (int, error) -// relationship github.com/golang/go/src go1.22 `crypto/tls`/transcriptHash#Write. implementation -// relationship github.com/golang/go/src go1.22 `internal/bisect`/Writer#Write. implementation -// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Write. implementation -// relationship github.com/golang/go/src go1.22 io/Writer#Write. implementation +// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Write(). implementation +// relationship github.com/golang/go/src go1.22 io/Writer#Write(). implementation // ⌃ enclosing_range_end 0.1.test `sg/impls`/MyWriter#Write(). //⌄ enclosing_range_start 0.1.test `sg/impls`/MyWriter#WriteHeader(). func (w MyWriter) WriteHeader(statusCode int) { panic("") } @@ -79,7 +75,7 @@ // display_name WriteHeader // signature_documentation // > func (MyWriter).WriteHeader(statusCode int) -// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#WriteHeader. implementation +// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#WriteHeader(). implementation // ^^^^^^^^^^ definition local 4 // kind Variable // display_name statusCode diff --git a/internal/testdata/snapshots/output/pr222/pr222.go b/internal/testdata/snapshots/output/pr222/pr222.go new file mode 100755 index 0000000..932b614 --- /dev/null +++ b/internal/testdata/snapshots/output/pr222/pr222.go @@ -0,0 +1,177 @@ + package pr222 +// ^^^^^ definition 0.1.test `sg/pr222`/ +// kind Package +// display_name pr222 +// signature_documentation +// > package pr222 + + import "github.com/example/deplib" +// ^^^^^^^^^^^^^^^^^^^^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/ + +//⌄ enclosing_range_start 0.1.test `sg/pr222`/UseGenericField(). + func UseGenericField() int { +// ^^^^^^^^^^^^^^^ definition 0.1.test `sg/pr222`/UseGenericField(). +// kind Function +// display_name UseGenericField +// signature_documentation +// > func UseGenericField() int + b := deplib.Box[int]{Value: 42} +// ^ definition local 0 +// kind Variable +// display_name b +// signature_documentation +// > var b Box[int] +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/ +// ^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Box# +// ^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Box#Value. + return b.Value +// ^ reference local 0 +// ^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Box#Value. + } +//⌃ enclosing_range_end 0.1.test `sg/pr222`/UseGenericField(). + +//⌄ enclosing_range_start 0.1.test `sg/pr222`/UseGenericMethod(). + func UseGenericMethod() string { +// ^^^^^^^^^^^^^^^^ definition 0.1.test `sg/pr222`/UseGenericMethod(). +// kind Function +// display_name UseGenericMethod +// signature_documentation +// > func UseGenericMethod() string + b := deplib.Box[string]{Value: "hello"} +// ^ definition local 1 +// kind Variable +// display_name b +// signature_documentation +// > var b Box[string] +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/ +// ^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Box# +// ^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Box#Value. + return b.Get() +// ^ reference local 1 +// ^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Box#Get(). + } +//⌃ enclosing_range_end 0.1.test `sg/pr222`/UseGenericMethod(). + +//⌄ enclosing_range_start 0.1.test `sg/pr222`/UseNonGenericField(). + func UseNonGenericField() string { +// ^^^^^^^^^^^^^^^^^^ definition 0.1.test `sg/pr222`/UseNonGenericField(). +// kind Function +// display_name UseNonGenericField +// signature_documentation +// > func UseNonGenericField() string + c := deplib.Config{Name: "test", Verbose: true} +// ^ definition local 2 +// kind Variable +// display_name c +// signature_documentation +// > var c Config +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/ +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Config# +// ^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Config#Name. +// ^^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Config#Verbose. + return c.Name +// ^ reference local 2 +// ^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Config#Name. + } +//⌃ enclosing_range_end 0.1.test `sg/pr222`/UseNonGenericField(). + +//⌄ enclosing_range_start 0.1.test `sg/pr222`/UseConst(). + func UseConst() string { +// ^^^^^^^^ definition 0.1.test `sg/pr222`/UseConst(). +// kind Function +// display_name UseConst +// signature_documentation +// > func UseConst() string + return deplib.DefaultName +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/ +// ^^^^^^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/DefaultName. + } +//⌃ enclosing_range_end 0.1.test `sg/pr222`/UseConst(). + +//⌄ enclosing_range_start 0.1.test `sg/pr222`/UseVar(). + func UseVar() int { +// ^^^^^^ definition 0.1.test `sg/pr222`/UseVar(). +// kind Function +// display_name UseVar +// signature_documentation +// > func UseVar() int + return deplib.GlobalCounter +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/ +// ^^^^^^^^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/GlobalCounter. + } +//⌃ enclosing_range_end 0.1.test `sg/pr222`/UseVar(). + + type LocalType struct{} +// ^^^^^^^^^ definition 0.1.test `sg/pr222`/LocalType# +// kind Struct +// display_name LocalType +// signature_documentation +// > type LocalType struct{} +// relationship github.com/example/deplib 0.1.test `github.com/example/deplib`/Stringer# implementation + +//⌄ enclosing_range_start 0.1.test `sg/pr222`/LocalType#String(). + func (l LocalType) String() string { return "local" } +// ^ definition local 3 +// kind Variable +// display_name l +// signature_documentation +// > var l LocalType +// ^^^^^^^^^ reference 0.1.test `sg/pr222`/LocalType# +// ^^^^^^ definition 0.1.test `sg/pr222`/LocalType#String(). +// kind Method +// display_name String +// signature_documentation +// > func (LocalType).String() string +// relationship github.com/example/deplib 0.1.test `github.com/example/deplib`/Stringer#String(). implementation +// ⌃ enclosing_range_end 0.1.test `sg/pr222`/LocalType#String(). + + type EmbeddedStringer struct { +// ^^^^^^^^^^^^^^^^ definition 0.1.test `sg/pr222`/EmbeddedStringer# +// kind Struct +// display_name EmbeddedStringer +// signature_documentation +// > type EmbeddedStringer struct{ LocalType } +// relationship github.com/example/deplib 0.1.test `github.com/example/deplib`/Stringer# implementation + LocalType +// ^^^^^^^^^ definition 0.1.test `sg/pr222`/EmbeddedStringer#LocalType. +// kind Field +// display_name LocalType +// signature_documentation +// > struct field LocalType LocalType +// ^^^^^^^^^ reference 0.1.test `sg/pr222`/LocalType# + } + + type LocalInterface interface { +// ^^^^^^^^^^^^^^ definition 0.1.test `sg/pr222`/LocalInterface# +// kind Interface +// display_name LocalInterface +// signature_documentation +// > type LocalInterface interface{ Get() int } + Get() int +// ^^^ definition 0.1.test `sg/pr222`/LocalInterface#Get. +// kind MethodSpecification +// display_name Get +// signature_documentation +// > func (LocalInterface).Get() int + } + +//⌄ enclosing_range_start 0.1.test `sg/pr222`/UseDepWriter(). + func UseDepWriter(w deplib.Writer) { +// ^^^^^^^^^^^^ definition 0.1.test `sg/pr222`/UseDepWriter(). +// kind Function +// display_name UseDepWriter +// signature_documentation +// > func UseDepWriter(w deplib.Writer) +// ^ definition local 4 +// kind Variable +// display_name w +// signature_documentation +// > var w Writer +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/ +// ^^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Writer# + w.Write(nil) +// ^ reference local 4 +// ^^^^^ reference github.com/example/deplib 0.1.test `github.com/example/deplib`/Writer#Write(). + } +//⌃ enclosing_range_end 0.1.test `sg/pr222`/UseDepWriter(). + diff --git a/internal/testdata/snapshots/output/testdata/implementations_remote.go b/internal/testdata/snapshots/output/testdata/implementations_remote.go index fd583d3..911d269 100755 --- a/internal/testdata/snapshots/output/testdata/implementations_remote.go +++ b/internal/testdata/snapshots/output/testdata/implementations_remote.go @@ -10,8 +10,6 @@ // display_name implementsWriter // signature_documentation // > type implementsWriter struct{} -// relationship github.com/golang/go/src go1.22 `crypto/tls`/transcriptHash# implementation -// relationship github.com/golang/go/src go1.22 `internal/bisect`/Writer# implementation // relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter# implementation // relationship github.com/golang/go/src go1.22 io/Writer# implementation @@ -23,7 +21,7 @@ // display_name Header // signature_documentation // > func (implementsWriter).Header() http.Header -// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Header. implementation +// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Header(). implementation // ^^^^ reference github.com/golang/go/src go1.22 `net/http`/ // ^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/Header# // ⌃ enclosing_range_end 0.1.test `sg/testdata`/implementsWriter#Header(). @@ -35,10 +33,8 @@ // display_name Write // signature_documentation // > func (implementsWriter).Write([]byte) (int, error) -// relationship github.com/golang/go/src go1.22 `crypto/tls`/transcriptHash#Write. implementation -// relationship github.com/golang/go/src go1.22 `internal/bisect`/Writer#Write. implementation -// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Write. implementation -// relationship github.com/golang/go/src go1.22 io/Writer#Write. implementation +// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#Write(). implementation +// relationship github.com/golang/go/src go1.22 io/Writer#Write(). implementation // ⌃ enclosing_range_end 0.1.test `sg/testdata`/implementsWriter#Write(). //⌄ enclosing_range_start 0.1.test `sg/testdata`/implementsWriter#WriteHeader(). func (implementsWriter) WriteHeader(statusCode int) {} @@ -48,7 +44,7 @@ // display_name WriteHeader // signature_documentation // > func (implementsWriter).WriteHeader(statusCode int) -// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#WriteHeader. implementation +// relationship github.com/golang/go/src go1.22 `net/http`/ResponseWriter#WriteHeader(). implementation // ^^^^^^^^^^ definition local 0 // kind Variable // display_name statusCode @@ -72,7 +68,7 @@ // ^^^^^^^^^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/ResponseWriter# respWriter.WriteHeader(1) // ^^^^^^^^^^ reference local 1 -// ^^^^^^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/ResponseWriter#WriteHeader. +// ^^^^^^^^^^^ reference github.com/golang/go/src go1.22 `net/http`/ResponseWriter#WriteHeader(). } //⌃ enclosing_range_end 0.1.test `sg/testdata`/ShowsInSignature().