Skip to content

Commit

Permalink
gopls/internal/golang: RenderPackageDoc: fix doc links
Browse files Browse the repository at this point in the history
Previously, a variety of links generated by the go/doc/comment
package were not valid. This CL configures the parser and printer
hooks to query the cache.Package representation, and now
generates valid crosslinks for all references.

Tested interactively on a wide variety of cross links.

Change-Id: Iaf1fffe52ea96e3be5df78227cf8395006caa59a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/575377
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
  • Loading branch information
adonovan authored and gopherbot committed Apr 3, 2024
1 parent 222cdde commit 53d35a5
Showing 1 changed file with 75 additions and 11 deletions.
86 changes: 75 additions & 11 deletions gopls/internal/golang/pkgdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ package golang
// - add push notifications such as didChange -> reload.
// - there appears to be a maximum file size beyond which the
// "source.doc" code action is not offered. Remove that.
// - modify JS httpGET function to give a transient visual indication
// when clicking a source link that the editor is being navigated
// (in case it doesn't raise itself, like VS Code).

import (
"bytes"
Expand All @@ -33,6 +36,7 @@ import (
"go/token"
"go/types"
"html"
"log"
"path/filepath"

"golang.org/x/tools/gopls/internal/cache"
Expand All @@ -41,6 +45,7 @@ import (
"golang.org/x/tools/gopls/internal/util/bug"
"golang.org/x/tools/gopls/internal/util/safetoken"
"golang.org/x/tools/gopls/internal/util/slices"
"golang.org/x/tools/gopls/internal/util/typesutil"
"golang.org/x/tools/internal/typesinternal"
)

Expand Down Expand Up @@ -108,13 +113,72 @@ func RenderPackageDoc(pkg *cache.Package, posURL func(filename string, line, col
})
}

// Ensure doc links (e.g. "[fmt.Println]") become valid links.
docpkg.Printer().DocLinkURL = func(link *comment.DocLink) string {
fragment := link.Name
if link.Recv == "" {
fragment = link.Recv + "." + link.Name
var docHTML func(comment string) []byte
{
// Adapt doc comment parser and printer
// to our representation of Go packages
// so that doc links (e.g. "[fmt.Println]")
// become valid links.

printer := docpkg.Printer()
printer.DocLinkURL = func(link *comment.DocLink) string {
path := pkg.Metadata().PkgPath
if link.ImportPath != "" {
path = PackagePath(link.ImportPath)
}
fragment := link.Name
if link.Recv != "" {
fragment = link.Recv + "." + link.Name
}
return pkgURL(path, fragment)
}
parser := docpkg.Parser()
parser.LookupPackage = func(name string) (importPath string, ok bool) {
// Ambiguous: different files in the same
// package may have different import mappings,
// but the hook doesn't provide the file context.
// TODO(adonovan): conspire with docHTML to
// pass the doc comment's enclosing file through
// a shared variable, so that we can compute
// the correct per-file mapping.
//
// TODO(adonovan): check for PkgName.Name
// matches, but also check for
// PkgName.Imported.Namer matches, since some
// packages are typically imported under a
// non-default name (e.g. pathpkg "path") but
// may be referred to in doc links using their
// canonical name.
for _, f := range pkg.Syntax() {
for _, imp := range f.Imports {
pkgName, ok := typesutil.ImportedPkgName(pkg.TypesInfo(), imp)
if ok && pkgName.Name() == name {
return pkgName.Imported().Path(), true
}
}
}
return "", false
}
parser.LookupSym = func(recv, name string) (ok bool) {
defer func() {
log.Printf("LookupSym %q %q = %t ", recv, name, ok)
}()
// package-level decl?
if recv == "" {
return pkg.Types().Scope().Lookup(name) != nil
}

// method?
tname, ok := pkg.Types().Scope().Lookup(recv).(*types.TypeName)
if !ok {
return false
}
m, _, _ := types.LookupFieldOrMethod(tname.Type(), true, pkg.Types(), name)
return is[*types.Func](m)
}
docHTML = func(comment string) []byte {
return printer.HTML(parser.Parse(comment))
}
return pkgURL(PackagePath(link.ImportPath), fragment)
}

var buf bytes.Buffer
Expand Down Expand Up @@ -302,7 +366,7 @@ function httpGET(url) {
"https://pkg.go.dev/"+string(pkg.Types().Path()))

// package doc
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docpkg.HTML(docpkg.Doc))
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docHTML(docpkg.Doc))

// symbol index
fmt.Fprintf(&buf, "<h2>Index</h2>\n")
Expand Down Expand Up @@ -366,7 +430,7 @@ function httpGET(url) {
fmt.Fprintf(&buf, "<pre class='code'>%s</pre>\n", nodeHTML(&decl2))

// comment (if any)
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docpkg.HTML(v.Doc))
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docHTML(v.Doc))
}
}
fmt.Fprintf(&buf, "<h2 id='hdr-Constants'>Constants</h2>\n")
Expand Down Expand Up @@ -397,7 +461,7 @@ function httpGET(url) {
nodeHTML(docfn.Decl.Type))

// comment (if any)
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docpkg.HTML(docfn.Doc))
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docHTML(docfn.Doc))
}
}
funcs(docpkg.Funcs)
Expand All @@ -417,7 +481,7 @@ function httpGET(url) {
fmt.Fprintf(&buf, "<pre class='code'>%s</pre>\n", nodeHTML(&decl2))

// comment (if any)
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docpkg.HTML(doctype.Doc))
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n", docHTML(doctype.Doc))

// subelements
values(doctype.Consts) // constants of type T
Expand All @@ -437,7 +501,7 @@ function httpGET(url) {

// comment (if any)
fmt.Fprintf(&buf, "<div class='comment'>%s</div>\n",
docpkg.HTML(docmethod.Doc))
docHTML(docmethod.Doc))
}
}

Expand Down

0 comments on commit 53d35a5

Please sign in to comment.