From 53d35a51d390a079bb6b6f2ba5e076f8a553d841 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 1 Apr 2024 18:41:43 -0400 Subject: [PATCH] gopls/internal/golang: RenderPackageDoc: fix doc links 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 Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI --- gopls/internal/golang/pkgdoc.go | 86 ++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/gopls/internal/golang/pkgdoc.go b/gopls/internal/golang/pkgdoc.go index b3525f8880b..852b93e7426 100644 --- a/gopls/internal/golang/pkgdoc.go +++ b/gopls/internal/golang/pkgdoc.go @@ -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" @@ -33,6 +36,7 @@ import ( "go/token" "go/types" "html" + "log" "path/filepath" "golang.org/x/tools/gopls/internal/cache" @@ -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" ) @@ -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 @@ -302,7 +366,7 @@ function httpGET(url) { "https://pkg.go.dev/"+string(pkg.Types().Path())) // package doc - fmt.Fprintf(&buf, "
%s
\n", docpkg.HTML(docpkg.Doc)) + fmt.Fprintf(&buf, "
%s
\n", docHTML(docpkg.Doc)) // symbol index fmt.Fprintf(&buf, "

Index

\n") @@ -366,7 +430,7 @@ function httpGET(url) { fmt.Fprintf(&buf, "
%s
\n", nodeHTML(&decl2)) // comment (if any) - fmt.Fprintf(&buf, "
%s
\n", docpkg.HTML(v.Doc)) + fmt.Fprintf(&buf, "
%s
\n", docHTML(v.Doc)) } } fmt.Fprintf(&buf, "

Constants

\n") @@ -397,7 +461,7 @@ function httpGET(url) { nodeHTML(docfn.Decl.Type)) // comment (if any) - fmt.Fprintf(&buf, "
%s
\n", docpkg.HTML(docfn.Doc)) + fmt.Fprintf(&buf, "
%s
\n", docHTML(docfn.Doc)) } } funcs(docpkg.Funcs) @@ -417,7 +481,7 @@ function httpGET(url) { fmt.Fprintf(&buf, "
%s
\n", nodeHTML(&decl2)) // comment (if any) - fmt.Fprintf(&buf, "
%s
\n", docpkg.HTML(doctype.Doc)) + fmt.Fprintf(&buf, "
%s
\n", docHTML(doctype.Doc)) // subelements values(doctype.Consts) // constants of type T @@ -437,7 +501,7 @@ function httpGET(url) { // comment (if any) fmt.Fprintf(&buf, "
%s
\n", - docpkg.HTML(docmethod.Doc)) + docHTML(docmethod.Doc)) } }