Skip to content

Commit 2bdbb87

Browse files
authored
feat(internal/godocfx): xref function declarations (#3615)
Type declarations are handled differently. So, I'll handle them in a future PR. There are some TODOs at the top of parse.go with additional ways we can improve. I chose to define linkify in parse.go rather than pkgsite because (1) we need the package/import name logic and (2) I want to keep changes to a minimum in the third_party code.
1 parent 176400b commit 2bdbb87

File tree

3 files changed

+329
-143
lines changed

3 files changed

+329
-143
lines changed

internal/godocfx/parse.go

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
// +build go1.15
1616

17+
// TODO:
18+
// pkgsite.PrintType doesn't linkify.
19+
// IDs for const/var groups have every name, not just the one to link to.
20+
// Preserve IDs when sanitizing then use the right ID for linking.
21+
// Link to different domains by pattern (e.g. for cloud.google.com/go).
22+
// Make sure dot imports work (those identifiers aren't in the current package).
23+
1724
package main
1825

1926
import (
@@ -148,7 +155,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
148155
// Once the files are grouped by package, process each package
149156
// independently.
150157
for _, pi := range pkgInfos {
151-
158+
link := newLinker(pi.pkg.Imports, pi.importRenames)
152159
pkgItem := &item{
153160
UID: pi.doc.ImportPath,
154161
Name: pi.doc.ImportPath,
@@ -255,7 +262,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
255262
Type: "function",
256263
Summary: fn.Doc,
257264
Langs: onlyGo,
258-
Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl)},
265+
Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl, link.linkify)},
259266
Examples: processExamples(fn.Examples, pi.fset),
260267
})
261268
}
@@ -270,7 +277,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
270277
Type: "method",
271278
Summary: fn.Doc,
272279
Langs: onlyGo,
273-
Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl)},
280+
Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl, link.linkify)},
274281
Examples: processExamples(fn.Examples, pi.fset),
275282
})
276283
}
@@ -286,7 +293,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
286293
Type: "function",
287294
Summary: fn.Doc,
288295
Langs: onlyGo,
289-
Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl)},
296+
Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl, link.linkify)},
290297
Examples: processExamples(fn.Examples, pi.fset),
291298
})
292299
}
@@ -300,6 +307,66 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
300307
}, nil
301308
}
302309

310+
type linker struct {
311+
// imports is a map from local package name to import path.
312+
// Behavior is undefined when a single import has different names in
313+
// different files.
314+
imports map[string]string
315+
}
316+
317+
func newLinker(rawImports map[string]*packages.Package, importSyntax map[string]string) *linker {
318+
imports := map[string]string{}
319+
for path, pkg := range rawImports {
320+
name := pkg.Name
321+
if rename := importSyntax[path]; rename != "" {
322+
name = rename
323+
}
324+
imports[name] = path
325+
}
326+
return &linker{imports: imports}
327+
}
328+
329+
func (l *linker) linkify(s string) string {
330+
prefix := ""
331+
if strings.HasPrefix(s, "...") {
332+
s = s[3:]
333+
prefix = "..."
334+
}
335+
if s[0] == '*' {
336+
s = s[1:]
337+
prefix += "*"
338+
}
339+
340+
// If s does not have a dot, it's in this package.
341+
if !strings.Contains(s, ".") {
342+
// If s is not exported, it's probably a builtin.
343+
if !token.IsExported(s) {
344+
if builtins[s] {
345+
return fmt.Sprintf(`%s<a href="https://pkg.go.dev/builtin#%s">%s</a>`, prefix, strings.ToLower(s), s)
346+
}
347+
return fmt.Sprintf("%s%s", prefix, s)
348+
}
349+
return fmt.Sprintf(`%s<a href="#%s">%s</a>`, prefix, strings.ToLower(s), s)
350+
}
351+
// Otherwise, it's in another package.
352+
split := strings.Split(s, ".")
353+
if len(split) != 2 {
354+
// Don't know how to link this.
355+
return fmt.Sprintf("%s%s", prefix, s)
356+
}
357+
358+
pkg := split[0]
359+
pkgPath, ok := l.imports[pkg]
360+
if !ok {
361+
// Don't know how to link this.
362+
return fmt.Sprintf("%s%s", prefix, s)
363+
}
364+
name := split[1]
365+
pkgLink := fmt.Sprintf("http://pkg.go.dev/%s", pkgPath)
366+
link := fmt.Sprintf("%s#%s", pkgLink, name)
367+
return fmt.Sprintf("%s<a href=%q>%s</a>.<a href=%q>%s</a>", prefix, pkgLink, pkg, link, name)
368+
}
369+
303370
// processExamples converts the examples to []example.
304371
//
305372
// Surrounding braces and indentation is removed.
@@ -400,11 +467,13 @@ type pkgInfo struct {
400467
pkg *packages.Package
401468
doc *doc.Package
402469
fset *token.FileSet
470+
// importRenames is a map from package path to local name or "".
471+
importRenames map[string]string
403472
}
404473

405474
func loadPackages(glob, workingDir string) ([]pkgInfo, error) {
406475
config := &packages.Config{
407-
Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedModule,
476+
Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedModule | packages.NeedImports,
408477
Tests: true,
409478
Dir: workingDir,
410479
}
@@ -494,12 +563,64 @@ func loadPackages(glob, workingDir string) ([]pkgInfo, error) {
494563
continue
495564
}
496565

566+
imports := map[string]string{}
567+
for _, f := range parsedFiles {
568+
for _, i := range f.Imports {
569+
name := ""
570+
// i.Name is nil for imports that aren't renamed.
571+
if i.Name != nil {
572+
name = i.Name.Name
573+
}
574+
iPath := strings.Trim(i.Path.Value, `"`)
575+
imports[iPath] = name
576+
}
577+
}
578+
497579
result = append(result, pkgInfo{
498-
pkg: idToPkg[pkgPath],
499-
doc: docPkg,
500-
fset: fset,
580+
pkg: idToPkg[pkgPath],
581+
doc: docPkg,
582+
fset: fset,
583+
importRenames: imports,
501584
})
502585
}
503586

504587
return result, nil
505588
}
589+
590+
var builtins = map[string]bool{
591+
"append": true,
592+
"cap": true,
593+
"close": true,
594+
"complex": true,
595+
"copy": true,
596+
"delete": true,
597+
"imag": true,
598+
"len": true,
599+
"make": true,
600+
"new": true,
601+
"panic": true,
602+
"print": true,
603+
"println": true,
604+
"real": true,
605+
"recover": true,
606+
"bool": true,
607+
"byte": true,
608+
"complex128": true,
609+
"complex64": true,
610+
"error": true,
611+
"float32": true,
612+
"float64": true,
613+
"int": true,
614+
"int16": true,
615+
"int32": true,
616+
"int64": true,
617+
"int8": true,
618+
"rune": true,
619+
"string": true,
620+
"uint": true,
621+
"uint16": true,
622+
"uint32": true,
623+
"uint64": true,
624+
"uint8": true,
625+
"uintptr": true,
626+
}

0 commit comments

Comments
 (0)