blob: 9683a7532d4e53244cbfd8cf7652aa4d1e092f89 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
import (
"fmt"
"sort"
"golang.org/x/mod/semver"
"golang.org/x/pkgsite/internal/derrors"
)
// SymbolSection is the documentation section where a symbol appears.
type SymbolSection string
const (
SymbolSectionConstants SymbolSection = "Constants"
SymbolSectionVariables SymbolSection = "Variables"
SymbolSectionFunctions SymbolSection = "Functions"
SymbolSectionTypes SymbolSection = "Types"
)
// SymbolKind is the type of a symbol.
type SymbolKind string
const (
SymbolKindConstant SymbolKind = "Constant"
SymbolKindVariable SymbolKind = "Variable"
SymbolKindFunction SymbolKind = "Function"
SymbolKindType SymbolKind = "Type"
SymbolKindField SymbolKind = "Field"
SymbolKindMethod SymbolKind = "Method"
)
// Symbol is an element in the package API. A symbol can be a constant,
// variable, function, or type.
type Symbol struct {
SymbolMeta
// Children contain the child symbols for this symbol. This will
// only be populated when the SymbolType is "Type". For example, the
// children of net/http.Handler are FileServer, NotFoundHandler,
// RedirectHandler, StripPrefix, and TimeoutHandler. Each child
// symbol will have ParentName set to the Name of this type.
Children []*SymbolMeta
// GOOS specifies the execution operating system where the symbol appears.
GOOS string
// GOARCH specifies the execution architecture where the symbol appears.
GOARCH string
}
// SymbolMeta is the metadata for an element in the package API. A symbol can
// be a constant, variable, function, or type.
type SymbolMeta struct {
// Name is the name of the symbol.
Name string
// Synopsis is the one line description of the symbol as displayed
// in the package documentation.
Synopsis string
// Section is the section that a symbol appears in.
Section SymbolSection
// Kind is the type of a symbol, which is either a constant, variable,
// function, type, field or method.
Kind SymbolKind
// ParentName if name of the parent type if available, otherwise
// the empty string. For example, the parent type for
// net/http.FileServer is Handler.
ParentName string
}
// SymbolHistory represents the history for when a symbol name was first added
// to a package.
type SymbolHistory struct {
// m is a map of version to name to SymbolMeta to UnitSymbol.
// SymbolMeta is stored as a distinct key from name, since it is possible
// for a symbol in the same version for different build contexts to have
// different SymbolMeta. For example:
// https://pkg.go.dev/syscall@go1.16.3#CloseOnExec has function signature:
// func CloseOnExec(fd int)
//
// versus
// https://pkg.go.dev/syscall?GOOS=windows#CloseOnExec has function
// signature:
// func CloseOnExec(fd Handle)
m map[string]map[string]map[SymbolMeta]*SymbolBuildContexts
}
// NewSymbolHistory returns a new *SymbolHistory.
func NewSymbolHistory() *SymbolHistory {
return &SymbolHistory{
m: map[string]map[string]map[SymbolMeta]*SymbolBuildContexts{},
}
}
// SymbolsAtVersion returns a map of name to SymbolMeta to UnitSymbol for a
// given version.
func (sh *SymbolHistory) SymbolsAtVersion(v string) map[string]map[SymbolMeta]*SymbolBuildContexts {
return sh.m[v]
}
// Versions returns an array of the versions in versionToNameToUnitSymbol, sorted by
// increasing semver.
func (sh *SymbolHistory) Versions() []string {
var orderdVersions []string
for v := range sh.m {
orderdVersions = append(orderdVersions, v)
}
sort.Slice(orderdVersions, func(i, j int) bool {
return semver.Compare(orderdVersions[i], orderdVersions[j]) == -1
})
return orderdVersions
}
// GetSymbol returns the unit symbol for a given name, version and build context.
func (sh *SymbolHistory) GetSymbol(name, v string, build BuildContext) (_ *SymbolMeta, err error) {
defer derrors.Wrap(&err, "GetSymbol(%q, %q, %v)", name, v, build)
sav, ok := sh.m[v]
if !ok {
return nil, fmt.Errorf("version %q could not be found: %q", v, name)
}
stu, ok := sav[name]
if !ok {
return nil, fmt.Errorf("symbol %q could not be found at version %q", name, v)
}
for sm, us := range stu {
if us.SupportsBuild(build) {
return &sm, nil
}
}
return nil, fmt.Errorf("symbol %q does not have build %v at version %q", name, build, v)
}
// AddSymbol adds the given symbol to SymbolHistory.
func (sh *SymbolHistory) AddSymbol(sm SymbolMeta, v string, build BuildContext) {
sav, ok := sh.m[v]
if !ok {
sav = map[string]map[SymbolMeta]*SymbolBuildContexts{}
sh.m[v] = sav
}
stu, ok := sav[sm.Name]
if !ok {
stu = map[SymbolMeta]*SymbolBuildContexts{}
sh.m[v][sm.Name] = stu
}
us, ok := stu[sm]
if !ok {
us = &SymbolBuildContexts{}
sh.m[v][sm.Name][sm] = us
}
us.AddBuildContext(build)
}
// SymbolBuildContexts represents the build contexts that are associated with a
// SymbolMeta.
type SymbolBuildContexts struct {
// builds are the build contexts that apply to this symbol.
builds map[BuildContext]bool
}
// BuildContexts returns the build contexts for this UnitSymbol.
func (us *SymbolBuildContexts) BuildContexts() []BuildContext {
var builds []BuildContext
for b := range us.builds {
builds = append(builds, b)
}
sort.Slice(builds, func(i, j int) bool {
return builds[i].GOOS < builds[j].GOOS
})
return builds
}
// AddBuildContext adds a build context supported by this UnitSymbol.
func (us *SymbolBuildContexts) AddBuildContext(build BuildContext) {
if us.builds == nil {
us.builds = map[BuildContext]bool{}
}
if build != BuildContextAll {
us.builds[build] = true
return
}
for _, b := range BuildContexts {
us.builds[b] = true
}
}
// SupportsBuild reports whether the provided build is supported by this
// UnitSymbol. If the build is BuildContextAll, this is interpreted as this
// unit symbol supports at least one build context.
func (us *SymbolBuildContexts) SupportsBuild(build BuildContext) bool {
if build == BuildContextAll {
return len(us.builds) > 0
}
return us.builds[build]
}
// InAll reports whether the unit symbol supports all build contexts.
func (us *SymbolBuildContexts) InAll() bool {
return len(us.builds) == len(BuildContexts)
}
// RemoveBuildContexts removes all of the build contexts associated with this
// unit symbol.
func (us *SymbolBuildContexts) RemoveBuildContexts() {
us.builds = map[BuildContext]bool{}
}