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+
1724package main
1825
1926import (
@@ -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
405474func 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