зеркало из https://github.com/golang/pkgsite.git
internal/frontend: add version history
Symbol history is added to the versions page, for when a symbol is first introduced to the package API. For golang/go#37102 Change-Id: I22b7bc959a464dc38fe0bd39244c997fd51370f1 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/298309 Trust: Julie Qiu <julie@golang.org> Run-TryBot: Julie Qiu <julie@golang.org> Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Родитель
ff0a2dbc60
Коммит
a2f4692c7e
|
@ -1460,11 +1460,14 @@ pre,
|
|||
}
|
||||
.Versions td:nth-child(1) {
|
||||
padding-right: 3rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
.Versions td:nth-child(2) {
|
||||
border-right: 1px solid var(--gray-8);
|
||||
padding-right: 1rem;
|
||||
text-align: right;
|
||||
border-right: 1px solid var(--gray-8);
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.Versions td:nth-child(3) {
|
||||
padding-left: 1rem;
|
||||
|
@ -1477,6 +1480,12 @@ pre,
|
|||
.Versions-major {
|
||||
font-weight: 600;
|
||||
}
|
||||
.Versions-symbolChild {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
.Versions-symbolBuilds {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.Imports-list {
|
||||
list-style: none;
|
||||
|
|
|
@ -47,8 +47,28 @@
|
|||
</td>
|
||||
<td>
|
||||
<div class="Versions-commitTime">{{$v.CommitTime}}</div>
|
||||
<div>
|
||||
{{range $v.Symbols}}
|
||||
{{template "symbol" .}}
|
||||
{{range .Children}}
|
||||
<div class="Versions-symbolChild">{{template "symbol" .}}</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "symbol"}}
|
||||
<div>
|
||||
{{if .New}}
|
||||
{{if eq .Kind "Method"}}method{{else if eq .Kind "Field"}}field{{end}}
|
||||
<a href="{{.Link}}">{{end}}{{.Synopsis}} {{if .New}}</a>
|
||||
{{end}}
|
||||
{{if .Builds}}
|
||||
<span class="Versions-symbolBuilds">{{range $i, $b := .Builds}}{{if $i}}, {{end}}{{$b}}{{end}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package internal
|
||||
|
||||
import "fmt"
|
||||
|
||||
// A BuildContext describes a build context for the Go tool: information needed
|
||||
// to build a Go package. For our purposes, we only care about the information
|
||||
// that affects documentation generated from the package.
|
||||
|
@ -11,6 +13,11 @@ type BuildContext struct {
|
|||
GOOS, GOARCH string
|
||||
}
|
||||
|
||||
// String returns a string formatted representation of the build context.
|
||||
func (b BuildContext) String() string {
|
||||
return fmt.Sprintf("%s/%s", b.GOOS, b.GOARCH)
|
||||
}
|
||||
|
||||
// All represents all values for a build context element (GOOS or GOARCH).
|
||||
const All = "all"
|
||||
|
||||
|
|
|
@ -6,25 +6,27 @@
|
|||
package internal
|
||||
|
||||
const (
|
||||
ExperimentCommandTOC = "command-toc"
|
||||
ExperimentInsertSymbols = "insert-symbols"
|
||||
ExperimentInsertSymbolHistory = "insert-symbol-history"
|
||||
ExperimentInteractivePlayground = "interactive-playground"
|
||||
ExperimentNotAtLatest = "not-at-latest"
|
||||
ExperimentRetractions = "retractions"
|
||||
ExperimentUnitMetaWithLatest = "unit-meta-with-latest"
|
||||
ExperimentCommandTOC = "command-toc"
|
||||
ExperimentInsertSymbolHistory = "insert-symbol-history"
|
||||
ExperimentInsertSymbols = "insert-symbols"
|
||||
ExperimentInteractivePlayground = "interactive-playground"
|
||||
ExperimentNotAtLatest = "not-at-latest"
|
||||
ExperimentRetractions = "retractions"
|
||||
ExperimentSymbolHistoryVersionsPage = "symbol-history-versions-page"
|
||||
ExperimentUnitMetaWithLatest = "unit-meta-with-latest"
|
||||
)
|
||||
|
||||
// Experiments represents all of the active experiments in the codebase and
|
||||
// a description of each experiment.
|
||||
var Experiments = map[string]string{
|
||||
ExperimentCommandTOC: "Enable the table of contents for command documention pages.",
|
||||
ExperimentInsertSymbols: "Insert data into symbols, package_symbols, and documentation_symbols.",
|
||||
ExperimentInsertSymbolHistory: "Insert symbol history data into the symbol_history table.",
|
||||
ExperimentInteractivePlayground: "Enable interactive example playground on the unit page.",
|
||||
ExperimentNotAtLatest: "Enable the display of a 'not at latest' badge.",
|
||||
ExperimentRetractions: "Retrieve and display retraction and deprecation information.",
|
||||
ExperimentUnitMetaWithLatest: "Use latest-version information for GetUnitMeta.",
|
||||
ExperimentCommandTOC: "Enable the table of contents for command documention pages.",
|
||||
ExperimentInsertSymbolHistory: "Insert symbol history data into the symbol_history table.",
|
||||
ExperimentInsertSymbols: "Insert data into symbols, package_symbols, and documentation_symbols.",
|
||||
ExperimentInteractivePlayground: "Enable interactive example playground on the unit page.",
|
||||
ExperimentNotAtLatest: "Enable the display of a 'not at latest' badge.",
|
||||
ExperimentRetractions: "Retrieve and display retraction and deprecation information.",
|
||||
ExperimentSymbolHistoryVersionsPage: "Show package API history on the versions page.",
|
||||
ExperimentUnitMetaWithLatest: "Use latest-version information for GetUnitMeta.",
|
||||
}
|
||||
|
||||
// Experiment holds data associated with an experimental feature for frontend
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
// 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 frontend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/pkgsite/internal"
|
||||
)
|
||||
|
||||
// Symbol is an element in the package API. A symbol can be a constant,
|
||||
// variable, function, type, field or method.
|
||||
type Symbol struct {
|
||||
// Name is name of the symbol. At a given package version, name must be
|
||||
// unique.
|
||||
Name string
|
||||
|
||||
// Synopsis is the one line description of the symbol that is displayed.
|
||||
Synopsis string
|
||||
|
||||
// Link is the link to the symbol name on pkg.go.dev.
|
||||
Link string
|
||||
|
||||
// Section is the section that a symbol appears in.
|
||||
Section internal.SymbolSection
|
||||
|
||||
// Kind is the type of a symbol, which is either a constant, variable,
|
||||
// function, type, field or method.
|
||||
Kind internal.SymbolKind
|
||||
|
||||
// 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 []*Symbol
|
||||
|
||||
// Builds lists all of the build contexts supported by the symbol, it is
|
||||
// only available for limited set of builds. If the symbol supports all
|
||||
// build contexts, Builds will be nil.
|
||||
Builds []string
|
||||
|
||||
// New indicates that the symbol is new as of the version where it is
|
||||
// present. For example, if type Client was introduced in v1.0.0 and
|
||||
// Client.Timeout was introduced in v1.1.0, New will be false for Client
|
||||
// and true for Client.Timeout if this Symbol corresponds to v1.1.0.
|
||||
New bool
|
||||
}
|
||||
|
||||
// symbolsForVersions returns an array of symbols for use in the VersionSummary
|
||||
// of the specified version.
|
||||
func symbolsForVersion(pkgURLPath string, symbolsAtVersion map[string]*internal.UnitSymbol) []*Symbol {
|
||||
nameToSymbol := map[string]*Symbol{}
|
||||
for _, us := range symbolsAtVersion {
|
||||
s, ok := nameToSymbol[us.Name]
|
||||
if !ok {
|
||||
s = &Symbol{
|
||||
Name: us.Name,
|
||||
Synopsis: formatSynopsis(us.Synopsis, us.ParentName, us.Kind),
|
||||
Link: symbolLink(pkgURLPath, us.Name),
|
||||
Section: us.Section,
|
||||
Kind: us.Kind,
|
||||
New: true,
|
||||
Builds: symbolBuilds(us),
|
||||
}
|
||||
} else if !s.New && us.Kind == internal.SymbolKindType {
|
||||
// It's possible that a symbol was already created if this is a parent
|
||||
// symbol and we called addSymbol on the child symbol first. In that
|
||||
// case, a parent symbol would have been created where s.New is set to
|
||||
// false and s.Synopsis is set to the one created in createParent.
|
||||
// Update those fields instead of overwritting the struct, since the
|
||||
// struct's Children field would have already been populated.
|
||||
s.New = true
|
||||
s.Synopsis = us.Synopsis
|
||||
}
|
||||
if us.ParentName == us.Name {
|
||||
// s is not a child symbol of a type, so add it to the map and
|
||||
// continue.
|
||||
nameToSymbol[us.Name] = s
|
||||
continue
|
||||
}
|
||||
|
||||
// s is a child symbol of a parent type, so append it to the Children field
|
||||
// of the parent type.
|
||||
parent, ok := nameToSymbol[us.ParentName]
|
||||
if !ok {
|
||||
parent = createParent(us, pkgURLPath)
|
||||
nameToSymbol[us.ParentName] = parent
|
||||
}
|
||||
parent.Children = append(parent.Children, s)
|
||||
}
|
||||
return sortSymbols(nameToSymbol)
|
||||
}
|
||||
|
||||
func symbolLink(pkgURLPath, name string) string {
|
||||
return fmt.Sprintf("%s#%s", pkgURLPath, name)
|
||||
}
|
||||
|
||||
func symbolBuilds(us *internal.UnitSymbol) []string {
|
||||
if us.InAll() {
|
||||
return nil
|
||||
}
|
||||
var builds []string
|
||||
for _, b := range us.BuildContexts() {
|
||||
builds = append(builds, b.String())
|
||||
}
|
||||
return builds
|
||||
}
|
||||
|
||||
// createParent creates a parent symbol for the provided unit symbol. This is
|
||||
// used when us is a child of a symbol that may have been introduced at a
|
||||
// different version. The symbol created will have New set to false, since this
|
||||
// function is only used when a parent symbol is not found for the unit symbol,
|
||||
// which means it was not introduced at the same version.
|
||||
func createParent(us *internal.UnitSymbol, pkgURLPath string) *Symbol {
|
||||
var synopsis string
|
||||
if strings.Contains(us.Synopsis, fmt.Sprintf("type %s struct", us.ParentName)) {
|
||||
synopsis = fmt.Sprintf("type %s struct", us.ParentName)
|
||||
} else if strings.Contains(us.Synopsis, fmt.Sprintf("type %s interface", us.ParentName)) {
|
||||
synopsis = fmt.Sprintf("type %s interface", us.ParentName)
|
||||
} else {
|
||||
synopsis = fmt.Sprintf("type %s", us.ParentName)
|
||||
}
|
||||
s := &Symbol{
|
||||
Name: us.ParentName,
|
||||
Synopsis: synopsis,
|
||||
Link: symbolLink(pkgURLPath, us.ParentName),
|
||||
Section: internal.SymbolSectionTypes,
|
||||
Kind: internal.SymbolKindType,
|
||||
Builds: symbolBuilds(us),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// formatSynopsis removes the leading "type <ParentName> <struct/interface>,"
|
||||
// string for the synopsis. These strings are added to prevent conflicts
|
||||
// between the same synopsis for different symbols (for example, if two
|
||||
// different interfaces both had a Foo() string method) in the database, since
|
||||
// the synopsis is used as a piece of a unique constraint in the database.
|
||||
//
|
||||
// Trailing brackets are removed from types.
|
||||
// TODO(https://golang.org/issue/37102): Change the unique constraint on
|
||||
// package_symbols and store the desired synopsis. Then remove this function.
|
||||
func formatSynopsis(syn, parentName string, kind internal.SymbolKind) string {
|
||||
switch kind {
|
||||
case internal.SymbolKindType:
|
||||
return strings.TrimSuffix(syn, "{}")
|
||||
case internal.SymbolKindField:
|
||||
return strings.TrimPrefix(syn, fmt.Sprintf("type %s struct, ", parentName))
|
||||
case internal.SymbolKindMethod:
|
||||
return strings.TrimPrefix(syn, fmt.Sprintf("type %s interface, ", parentName))
|
||||
}
|
||||
return syn
|
||||
}
|
||||
|
||||
// sortSymbols returns an array of symbols in order of
|
||||
// (1) Constants (2) Variables (3) Functions and (4) Types.
|
||||
// Within each section, symbols are sorted alphabetically by name.
|
||||
// In the types sections, aside from interfaces, child symbols are sorted in
|
||||
// order of (1) Fields (2) Constants (3) Variables (4) Functions and (5)
|
||||
// Methods. For interfaces, child symbols are sorted in order of
|
||||
// (1) Methods (2) Constants (3) Variables and (4) Functions.
|
||||
func sortSymbols(nameToSymbol map[string]*Symbol) []*Symbol {
|
||||
sm := map[internal.SymbolSection][]*Symbol{}
|
||||
for _, parent := range nameToSymbol {
|
||||
sm[parent.Section] = append(sm[parent.Section], parent)
|
||||
|
||||
cm := map[internal.SymbolKind][]*Symbol{}
|
||||
parent.Synopsis = strings.TrimSuffix(parent.Synopsis, "{ ... }")
|
||||
for _, c := range parent.Children {
|
||||
cm[c.Kind] = append(cm[c.Kind], c)
|
||||
}
|
||||
for _, syms := range cm {
|
||||
sortSymbolsGroup(syms)
|
||||
}
|
||||
symbols := append(append(append(
|
||||
cm[internal.SymbolKindField],
|
||||
cm[internal.SymbolKindConstant]...),
|
||||
cm[internal.SymbolKindVariable]...),
|
||||
cm[internal.SymbolKindFunction]...)
|
||||
if strings.Contains(parent.Synopsis, "interface") {
|
||||
parent.Children = append(cm[internal.SymbolKindMethod], symbols...)
|
||||
} else {
|
||||
parent.Children = append(symbols, cm[internal.SymbolKindMethod]...)
|
||||
}
|
||||
}
|
||||
for _, syms := range sm {
|
||||
sortSymbolsGroup(syms)
|
||||
}
|
||||
return append(append(append(
|
||||
sm[internal.SymbolSectionConstants],
|
||||
sm[internal.SymbolSectionVariables]...),
|
||||
sm[internal.SymbolSectionFunctions]...),
|
||||
sm[internal.SymbolSectionTypes]...)
|
||||
}
|
||||
|
||||
func sortSymbolsGroup(syms []*Symbol) {
|
||||
sort.Slice(syms, func(i, j int) bool {
|
||||
return syms[i].Synopsis < syms[j].Synopsis
|
||||
})
|
||||
for _, s := range syms {
|
||||
sort.Strings(s.Builds)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import (
|
|||
"golang.org/x/pkgsite/internal/log"
|
||||
"golang.org/x/pkgsite/internal/postgres"
|
||||
"golang.org/x/pkgsite/internal/stdlib"
|
||||
"golang.org/x/pkgsite/internal/symbol"
|
||||
"golang.org/x/pkgsite/internal/version"
|
||||
)
|
||||
|
||||
|
@ -77,6 +78,7 @@ type VersionSummary struct {
|
|||
Version string
|
||||
Retracted bool
|
||||
RetractionRationale string
|
||||
Symbols []*Symbol
|
||||
}
|
||||
|
||||
func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, fullPath, modulePath string) (*VersionsDetails, error) {
|
||||
|
@ -89,6 +91,15 @@ func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, fullPath,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outVersionToNameToUnitSymbol := map[string]map[string]*internal.UnitSymbol{}
|
||||
if experiment.IsActive(ctx, internal.ExperimentSymbolHistoryVersionsPage) {
|
||||
versionToNameToUnitSymbols, err := db.GetPackageSymbols(ctx, fullPath, modulePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outVersionToNameToUnitSymbol = symbol.IntroducedHistory(versionToNameToUnitSymbols)
|
||||
}
|
||||
linkify := func(mi *internal.ModuleInfo) string {
|
||||
// Here we have only version information, but need to construct the full
|
||||
// import path of the package corresponding to this version.
|
||||
|
@ -100,7 +111,7 @@ func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, fullPath,
|
|||
}
|
||||
return constructUnitURL(versionPath, mi.ModulePath, linkVersion(mi.Version, mi.ModulePath))
|
||||
}
|
||||
return buildVersionDetails(ctx, modulePath, versions, linkify), nil
|
||||
return buildVersionDetails(ctx, modulePath, versions, outVersionToNameToUnitSymbol, linkify), nil
|
||||
}
|
||||
|
||||
// pathInVersion constructs the full import path of the package corresponding
|
||||
|
@ -127,7 +138,11 @@ func pathInVersion(v1Path string, mi *internal.ModuleInfo) string {
|
|||
// versions tab, organizing major versions into those that have the same module
|
||||
// path as the package version under consideration, and those that don't. The
|
||||
// given versions MUST be sorted first by module path and then by semver.
|
||||
func buildVersionDetails(ctx context.Context, currentModulePath string, modInfos []*internal.ModuleInfo, linkify func(v *internal.ModuleInfo) string) *VersionsDetails {
|
||||
func buildVersionDetails(ctx context.Context,
|
||||
currentModulePath string,
|
||||
modInfos []*internal.ModuleInfo,
|
||||
versionToNameToSymbol map[string]map[string]*internal.UnitSymbol,
|
||||
linkify func(v *internal.ModuleInfo) string) *VersionsDetails {
|
||||
// lists organizes versions by VersionListKey. Note that major version isn't
|
||||
// sufficient as a key: there are packages contained in the same major
|
||||
// version of different modules, for example github.com/hashicorp/vault/api,
|
||||
|
@ -179,6 +194,9 @@ func buildVersionDetails(ctx context.Context, currentModulePath string, modInfos
|
|||
vs.Retracted = mi.Retracted
|
||||
vs.RetractionRationale = mi.RetractionRationale
|
||||
}
|
||||
if nts, ok := versionToNameToSymbol[mi.Version]; ok {
|
||||
vs.Symbols = symbolsForVersion(linkify(mi), nts)
|
||||
}
|
||||
if _, ok := lists[key]; !ok {
|
||||
seenLists = append(seenLists, key)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/pkgsite/internal"
|
||||
"golang.org/x/pkgsite/internal/experiment"
|
||||
"golang.org/x/pkgsite/internal/postgres"
|
||||
"golang.org/x/pkgsite/internal/testing/sample"
|
||||
"golang.org/x/pkgsite/internal/version"
|
||||
|
@ -44,9 +45,6 @@ func versionSummaries(path string, versions []string, linkify func(path, version
|
|||
}
|
||||
|
||||
func TestFetchPackageVersionsDetails(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout*2)
|
||||
defer cancel()
|
||||
|
||||
var (
|
||||
v2Path = "test.com/module/v2/foo"
|
||||
v1Path = "test.com/module/foo"
|
||||
|
@ -161,6 +159,9 @@ func TestFetchPackageVersionsDetails(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testTimeout*2)
|
||||
ctx = experiment.NewContext(ctx, internal.ExperimentInsertSymbols, internal.ExperimentSymbolHistoryVersionsPage)
|
||||
defer cancel()
|
||||
defer postgres.ResetTestDB(testDB, t)
|
||||
|
||||
for _, v := range tc.modules {
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
A module used for testing the symbols logic.
|
||||
|
||||
-- go.mod --
|
||||
module example.com/symbols
|
||||
|
||||
-- README.md --
|
||||
This is the README for a test module.
|
||||
|
||||
-- LICENSE --
|
||||
$MITLicense
|
||||
|
||||
-- symbols.go --
|
||||
package symbols
|
||||
|
||||
// const
|
||||
const C = 1
|
||||
|
||||
// const iota
|
||||
const (
|
||||
AA = iota + 1
|
||||
_
|
||||
BB
|
||||
CC
|
||||
)
|
||||
|
||||
type Num int
|
||||
|
||||
const (
|
||||
DD Num = iota
|
||||
_
|
||||
EE
|
||||
FF
|
||||
)
|
||||
|
||||
// var
|
||||
var V = 2
|
||||
|
||||
// Multiple variables on the same line.
|
||||
var A, B string
|
||||
|
||||
// func
|
||||
func F() {}
|
||||
|
||||
// type
|
||||
type T int
|
||||
|
||||
// typeConstant
|
||||
const CT T = 3
|
||||
|
||||
// typeVariable
|
||||
var VT T
|
||||
|
||||
// multi-line var
|
||||
var (
|
||||
ErrA = errors.New("error A")
|
||||
ErrB = errors.New("error B")
|
||||
)
|
||||
|
||||
// typeFunc
|
||||
func TF() T { return T(0) }
|
||||
|
||||
// method
|
||||
// BUG(uid): this verifies that notes are rendered
|
||||
func (T) M() {}
|
||||
|
||||
type S1 struct {
|
||||
F int // field
|
||||
}
|
||||
|
||||
type S2 struct {
|
||||
S1 // embedded struct; should have an id
|
||||
}
|
||||
|
||||
type I1 interface {
|
||||
M1()
|
||||
}
|
||||
|
||||
type I2 interface {
|
||||
I1 // embedded interface; should not have an id
|
||||
}
|
||||
|
||||
type (
|
||||
Int int
|
||||
)
|
|
@ -0,0 +1,108 @@
|
|||
A module used for testing the symbols logic.
|
||||
|
||||
-- go.mod --
|
||||
module example.com/symbols
|
||||
|
||||
-- README.md --
|
||||
This is the README for a test module.
|
||||
|
||||
-- LICENSE --
|
||||
$MITLicense
|
||||
|
||||
-- symbols.go --
|
||||
package symbols
|
||||
|
||||
// const
|
||||
const C = 1
|
||||
|
||||
// const iota
|
||||
const (
|
||||
AA = iota + 1
|
||||
_
|
||||
BB
|
||||
CC
|
||||
)
|
||||
|
||||
type Num int
|
||||
|
||||
const (
|
||||
DD Num = iota
|
||||
_
|
||||
EE
|
||||
FF
|
||||
)
|
||||
|
||||
// var
|
||||
var V = 2
|
||||
|
||||
// Multiple variables on the same line.
|
||||
var A, B string
|
||||
|
||||
// func
|
||||
func F() {}
|
||||
|
||||
// type
|
||||
type T int
|
||||
|
||||
// typeConstant
|
||||
const CT T = 3
|
||||
|
||||
// typeVariable
|
||||
var VT T
|
||||
|
||||
// multi-line var
|
||||
var (
|
||||
ErrA = errors.New("error A")
|
||||
ErrB = errors.New("error B")
|
||||
)
|
||||
|
||||
// typeFunc
|
||||
func TF() T { return T(0) }
|
||||
|
||||
// method
|
||||
// BUG(uid): this verifies that notes are rendered
|
||||
func (T) M() {}
|
||||
|
||||
type S1 struct {
|
||||
F int // field
|
||||
}
|
||||
|
||||
type S2 struct {
|
||||
S1 // embedded struct; should have an id
|
||||
G int
|
||||
}
|
||||
|
||||
type I1 interface {
|
||||
M1()
|
||||
}
|
||||
|
||||
type I2 interface {
|
||||
I1 // embedded interface; should not have an id
|
||||
M2()
|
||||
}
|
||||
|
||||
type (
|
||||
Int int
|
||||
String bool
|
||||
)
|
||||
|
||||
-- hello/hello.go --
|
||||
// +build linux darwin
|
||||
// +build amd64
|
||||
|
||||
package hello
|
||||
|
||||
// Hello returns a greeting.
|
||||
func Hello() string {
|
||||
return "Hello"
|
||||
}
|
||||
|
||||
-- hello/hello_js.go --
|
||||
// +build js,wasm
|
||||
|
||||
package hello
|
||||
|
||||
// HelloJS returns a greeting when the build context is js/wasm.
|
||||
func HelloJS() string {
|
||||
return "Hello"
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
A module used for testing the symbols logic.
|
||||
|
||||
-- go.mod --
|
||||
module example.com/symbols
|
||||
|
||||
-- README.md --
|
||||
This is the README for a test module.
|
||||
|
||||
-- LICENSE --
|
||||
$MITLicense
|
||||
|
||||
-- symbols.go --
|
||||
package symbols
|
||||
|
||||
// const
|
||||
const C = 1
|
||||
|
||||
// const iota
|
||||
const (
|
||||
AA = iota + 1
|
||||
_
|
||||
BB
|
||||
CC
|
||||
)
|
||||
|
||||
type Num int
|
||||
|
||||
const (
|
||||
DD Num = iota
|
||||
_
|
||||
EE
|
||||
FF
|
||||
)
|
||||
|
||||
// var
|
||||
var V = 2
|
||||
|
||||
// Multiple variables on the same line.
|
||||
var A, B string
|
||||
|
||||
// func
|
||||
func F() {}
|
||||
|
||||
// type
|
||||
type T int
|
||||
|
||||
// typeConstant
|
||||
const CT T = 3
|
||||
|
||||
// typeVariable
|
||||
var VT T
|
||||
|
||||
// multi-line var
|
||||
var (
|
||||
ErrA = errors.New("error A")
|
||||
ErrB = errors.New("error B")
|
||||
)
|
||||
|
||||
// typeFunc
|
||||
func TF() T { return T(0) }
|
||||
|
||||
// method
|
||||
// BUG(uid): this verifies that notes are rendered
|
||||
func (T) M() {}
|
||||
|
||||
type S1 struct {
|
||||
F int // field
|
||||
}
|
||||
|
||||
type S2 struct {
|
||||
S1 // embedded struct; should have an id
|
||||
G int
|
||||
}
|
||||
|
||||
type I1 interface {
|
||||
M1()
|
||||
}
|
||||
|
||||
type I2 interface {
|
||||
I1 // embedded interface; should not have an id
|
||||
M2()
|
||||
}
|
||||
|
||||
type (
|
||||
Int int
|
||||
String bool
|
||||
)
|
||||
|
||||
-- hello/hello.go --
|
||||
package hello
|
||||
|
||||
// Hello returns a greeting.
|
||||
func Hello() string {
|
||||
return "Hello"
|
||||
}
|
|
@ -29,7 +29,7 @@ const (
|
|||
)
|
||||
|
||||
// Symbol is an element in the package API. A symbol can be a constant,
|
||||
// variable, function of type.
|
||||
// variable, function, or type.
|
||||
type Symbol struct {
|
||||
// Name is the name of the symbol.
|
||||
Name string
|
||||
|
|
|
@ -0,0 +1,354 @@
|
|||
// 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 integration
|
||||
|
||||
import "golang.org/x/pkgsite/internal/frontend"
|
||||
|
||||
var versionsPageHello = []*frontend.VersionList{
|
||||
{
|
||||
VersionListKey: frontend.VersionListKey{
|
||||
ModulePath: "example.com/symbols",
|
||||
Major: "v1",
|
||||
},
|
||||
Versions: []*frontend.VersionSummary{
|
||||
{
|
||||
CommitTime: "Jan 30, 2019",
|
||||
Link: "/example.com/symbols@v1.2.0/hello",
|
||||
Retracted: false,
|
||||
RetractionRationale: "",
|
||||
Version: "v1.2.0",
|
||||
Symbols: []*frontend.Symbol{
|
||||
{
|
||||
Name: "Hello",
|
||||
Synopsis: "func Hello() string",
|
||||
Link: "/example.com/symbols@v1.2.0/hello#Hello",
|
||||
New: true,
|
||||
Section: "Functions",
|
||||
Kind: "Function",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CommitTime: "Jan 30, 2019",
|
||||
Link: "/example.com/symbols@v1.1.0/hello",
|
||||
Retracted: false,
|
||||
RetractionRationale: "",
|
||||
Version: "v1.1.0",
|
||||
Symbols: []*frontend.Symbol{
|
||||
{
|
||||
Name: "Hello",
|
||||
Synopsis: "func Hello() string",
|
||||
Link: "/example.com/symbols@v1.1.0/hello#Hello",
|
||||
New: true,
|
||||
Section: "Functions",
|
||||
Kind: "Function",
|
||||
Builds: []string{"darwin/amd64", "linux/amd64"},
|
||||
},
|
||||
{
|
||||
Name: "HelloJS",
|
||||
Synopsis: "func HelloJS() string",
|
||||
Link: "/example.com/symbols@v1.1.0/hello#HelloJS",
|
||||
New: true,
|
||||
Section: "Functions",
|
||||
Kind: "Function",
|
||||
Builds: []string{"js/wasm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var versionsPageSymbols = []*frontend.VersionList{
|
||||
{
|
||||
VersionListKey: frontend.VersionListKey{
|
||||
ModulePath: "example.com/symbols",
|
||||
Major: "v1",
|
||||
},
|
||||
Versions: []*frontend.VersionSummary{
|
||||
{
|
||||
CommitTime: "Jan 30, 2019",
|
||||
Link: "/example.com/symbols@v1.2.0",
|
||||
Version: "v1.2.0",
|
||||
},
|
||||
{
|
||||
CommitTime: "Jan 30, 2019",
|
||||
Link: "/example.com/symbols@v1.1.0",
|
||||
Version: "v1.1.0",
|
||||
Symbols: []*frontend.Symbol{
|
||||
{
|
||||
Name: "I2",
|
||||
Synopsis: "type I2 interface",
|
||||
Link: "/example.com/symbols@v1.1.0#I2",
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
Children: []*frontend.Symbol{
|
||||
{
|
||||
Name: "I2.M2",
|
||||
Synopsis: "M2 func()",
|
||||
Link: "/example.com/symbols@v1.1.0#I2.M2",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Method",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "S2",
|
||||
Synopsis: "type S2 struct",
|
||||
Link: "/example.com/symbols@v1.1.0#S2",
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
Children: []*frontend.Symbol{
|
||||
{
|
||||
Name: "S2.G",
|
||||
Synopsis: "G int",
|
||||
Link: "/example.com/symbols@v1.1.0#S2.G",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Field",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "String",
|
||||
Synopsis: "type String bool",
|
||||
Link: "/example.com/symbols@v1.1.0#String",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CommitTime: "Jan 30, 2019",
|
||||
Link: "/example.com/symbols@v1.0.0",
|
||||
Retracted: false,
|
||||
RetractionRationale: "",
|
||||
Version: "v1.0.0",
|
||||
Symbols: []*frontend.Symbol{
|
||||
{
|
||||
Name: "AA",
|
||||
Synopsis: "const AA",
|
||||
Link: "/example.com/symbols@v1.0.0#AA",
|
||||
New: true,
|
||||
Section: "Constants",
|
||||
Kind: "Constant",
|
||||
},
|
||||
{
|
||||
Name: "BB",
|
||||
Synopsis: "const BB",
|
||||
Link: "/example.com/symbols@v1.0.0#BB",
|
||||
New: true,
|
||||
Section: "Constants",
|
||||
Kind: "Constant",
|
||||
},
|
||||
{
|
||||
Name: "C",
|
||||
Synopsis: "const C",
|
||||
Link: "/example.com/symbols@v1.0.0#C",
|
||||
New: true,
|
||||
Section: "Constants",
|
||||
Kind: "Constant",
|
||||
},
|
||||
{
|
||||
Name: "CC",
|
||||
Synopsis: "const CC",
|
||||
Link: "/example.com/symbols@v1.0.0#CC",
|
||||
New: true,
|
||||
Section: "Constants",
|
||||
Kind: "Constant",
|
||||
},
|
||||
{
|
||||
Name: "A",
|
||||
Synopsis: "var A string",
|
||||
Link: "/example.com/symbols@v1.0.0#A",
|
||||
New: true,
|
||||
Section: "Variables",
|
||||
Kind: "Variable",
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
Synopsis: "var B string",
|
||||
Link: "/example.com/symbols@v1.0.0#B",
|
||||
New: true,
|
||||
Section: "Variables",
|
||||
Kind: "Variable",
|
||||
},
|
||||
{
|
||||
|
||||
Name: "ErrA",
|
||||
Synopsis: `var ErrA = errors.New("error A")`,
|
||||
Link: "/example.com/symbols@v1.0.0#ErrA",
|
||||
New: true,
|
||||
Section: "Variables",
|
||||
Kind: "Variable",
|
||||
},
|
||||
{
|
||||
|
||||
Name: "ErrB",
|
||||
Synopsis: `var ErrB = errors.New("error B")`,
|
||||
Link: "/example.com/symbols@v1.0.0#ErrB",
|
||||
New: true,
|
||||
Section: "Variables",
|
||||
Kind: "Variable",
|
||||
},
|
||||
{
|
||||
Name: "V",
|
||||
Synopsis: "var V = 2",
|
||||
Link: "/example.com/symbols@v1.0.0#V",
|
||||
New: true,
|
||||
Section: "Variables",
|
||||
Kind: "Variable",
|
||||
},
|
||||
{
|
||||
Name: "F",
|
||||
Synopsis: "func F()",
|
||||
Link: "/example.com/symbols@v1.0.0#F",
|
||||
New: true,
|
||||
Section: "Functions",
|
||||
Kind: "Function",
|
||||
},
|
||||
{
|
||||
Name: "I1",
|
||||
Synopsis: "type I1 interface",
|
||||
Link: "/example.com/symbols@v1.0.0#I1",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
Children: []*frontend.Symbol{
|
||||
{
|
||||
Name: "I1.M1",
|
||||
Synopsis: "M1 func()",
|
||||
Link: "/example.com/symbols@v1.0.0#I1.M1",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Method",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "I2",
|
||||
Synopsis: "type I2 interface",
|
||||
Link: "/example.com/symbols@v1.0.0#I2",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
},
|
||||
{
|
||||
Name: "Int",
|
||||
Synopsis: "type Int int",
|
||||
Link: "/example.com/symbols@v1.0.0#Int",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
},
|
||||
{
|
||||
Name: "Num",
|
||||
Synopsis: "type Num int",
|
||||
Link: "/example.com/symbols@v1.0.0#Num",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
Children: []*frontend.Symbol{
|
||||
{
|
||||
Name: "DD",
|
||||
Synopsis: "const DD",
|
||||
Link: "/example.com/symbols@v1.0.0#DD",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Constant",
|
||||
},
|
||||
{
|
||||
Name: "EE",
|
||||
Synopsis: "const EE",
|
||||
Link: "/example.com/symbols@v1.0.0#EE",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Constant",
|
||||
},
|
||||
{
|
||||
Name: "FF",
|
||||
Synopsis: "const FF",
|
||||
Link: "/example.com/symbols@v1.0.0#FF",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Constant",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "S1",
|
||||
Synopsis: "type S1 struct",
|
||||
Link: "/example.com/symbols@v1.0.0#S1",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
Children: []*frontend.Symbol{
|
||||
{
|
||||
Name: "S1.F",
|
||||
Synopsis: "F int",
|
||||
Link: "/example.com/symbols@v1.0.0#S1.F",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Field",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "S2",
|
||||
Synopsis: "type S2 struct",
|
||||
Link: "/example.com/symbols@v1.0.0#S2",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
},
|
||||
{
|
||||
Name: "T",
|
||||
Synopsis: "type T int",
|
||||
Link: "/example.com/symbols@v1.0.0#T",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Type",
|
||||
Children: []*frontend.Symbol{
|
||||
{
|
||||
Name: "CT",
|
||||
Synopsis: "const CT",
|
||||
Link: "/example.com/symbols@v1.0.0#CT",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Constant",
|
||||
},
|
||||
{
|
||||
Name: "VT",
|
||||
Synopsis: "var VT T",
|
||||
Link: "/example.com/symbols@v1.0.0#VT",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Variable",
|
||||
},
|
||||
{
|
||||
Name: "TF",
|
||||
Synopsis: "func TF() T",
|
||||
Link: "/example.com/symbols@v1.0.0#TF",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Function",
|
||||
},
|
||||
{
|
||||
Name: "T.M",
|
||||
Synopsis: "func (T) M()",
|
||||
Link: "/example.com/symbols@v1.0.0#T.M",
|
||||
New: true,
|
||||
Section: "Types",
|
||||
Kind: "Method",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -39,9 +39,13 @@ func TestFrontendDocRender(t *testing.T) {
|
|||
}
|
||||
|
||||
func getDoc(t *testing.T, modulePath string, exps ...string) string {
|
||||
return getFrontendPage(t, "/"+modulePath, exps...)
|
||||
}
|
||||
|
||||
func getFrontendPage(t *testing.T, urlPath string, exps ...string) string {
|
||||
ctx := experiment.NewContext(context.Background(), exps...)
|
||||
ts := setupFrontend(ctx, t, nil, nil)
|
||||
url := ts.URL + "/" + modulePath
|
||||
url := ts.URL + urlPath
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", url, err)
|
||||
|
|
|
@ -36,6 +36,7 @@ func setupFrontend(ctx context.Context, t *testing.T, q queue.Queue, rc *redis.C
|
|||
ThirdPartyPath: "../../../third_party",
|
||||
AppVersionLabel: "",
|
||||
Queue: q,
|
||||
ServeStats: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// 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 integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/pkgsite/internal"
|
||||
"golang.org/x/pkgsite/internal/experiment"
|
||||
"golang.org/x/pkgsite/internal/frontend"
|
||||
"golang.org/x/pkgsite/internal/postgres"
|
||||
)
|
||||
|
||||
func TestFrontendVersionsPage(t *testing.T) {
|
||||
defer postgres.ResetTestDB(testDB, t)
|
||||
|
||||
exps := []string{
|
||||
internal.ExperimentInsertSymbols,
|
||||
internal.ExperimentSymbolHistoryVersionsPage,
|
||||
}
|
||||
processVersions(
|
||||
experiment.NewContext(context.Background(), exps...),
|
||||
t, testModules)
|
||||
|
||||
const modulePath = "example.com/symbols"
|
||||
for _, test := range []struct {
|
||||
name, pkgPath string
|
||||
want []*frontend.VersionList
|
||||
}{
|
||||
{"versions page symbols - one version all symbols", modulePath, versionsPageSymbols},
|
||||
{"versions page hello - multi GOOS", modulePath + "/hello", versionsPageHello},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
urlPath := fmt.Sprintf("/%s?tab=versions&m=json", test.pkgPath)
|
||||
body := getFrontendPage(t, urlPath, exps...)
|
||||
var got frontend.VersionsDetails
|
||||
if err := json.Unmarshal([]byte(body), &got); err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(test.want, got.ThisModule); diff != "" {
|
||||
t.Errorf("mismatch (-want, got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче