@@ -34,6 +34,7 @@ import (
3434 "log"
3535 "os"
3636 "path/filepath"
37+ "regexp"
3738 "sort"
3839 "strconv"
3940 "strings"
@@ -155,7 +156,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
155156 // Once the files are grouped by package, process each package
156157 // independently.
157158 for _ , pi := range pkgInfos {
158- link := newLinker (pi . pkg . Imports , pi . importRenames )
159+ link := newLinker (pi )
159160 topLevelDecls := pkgsite .TopLevelDecls (pi .doc )
160161 pkgItem := & item {
161162 UID : pi .doc .ImportPath ,
@@ -183,7 +184,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
183184 Type : "const" ,
184185 Summary : c .Doc ,
185186 Langs : onlyGo ,
186- Syntax : syntax {Content : pkgsite .PrintType (pi .fset , c .Decl , toURL , topLevelDecls )},
187+ Syntax : syntax {Content : pkgsite .PrintType (pi .fset , c .Decl , link . toURL , topLevelDecls )},
187188 })
188189 }
189190 for _ , v := range pi .doc .Vars {
@@ -199,7 +200,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
199200 Type : "variable" ,
200201 Summary : v .Doc ,
201202 Langs : onlyGo ,
202- Syntax : syntax {Content : pkgsite .PrintType (pi .fset , v .Decl , toURL , topLevelDecls )},
203+ Syntax : syntax {Content : pkgsite .PrintType (pi .fset , v .Decl , link . toURL , topLevelDecls )},
203204 })
204205 }
205206 for _ , t := range pi .doc .Types {
@@ -213,7 +214,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
213214 Type : "type" ,
214215 Summary : t .Doc ,
215216 Langs : onlyGo ,
216- Syntax : syntax {Content : pkgsite .PrintType (pi .fset , t .Decl , toURL , topLevelDecls )},
217+ Syntax : syntax {Content : pkgsite .PrintType (pi .fset , t .Decl , link . toURL , topLevelDecls )},
217218 Examples : processExamples (t .Examples , pi .fset ),
218219 }
219220 // Note: items are added as page.Children, rather than
@@ -232,7 +233,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
232233 Type : "const" ,
233234 Summary : c .Doc ,
234235 Langs : onlyGo ,
235- Syntax : syntax {Content : pkgsite .PrintType (pi .fset , c .Decl , toURL , topLevelDecls )},
236+ Syntax : syntax {Content : pkgsite .PrintType (pi .fset , c .Decl , link . toURL , topLevelDecls )},
236237 })
237238 }
238239 for _ , v := range t .Vars {
@@ -248,7 +249,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
248249 Type : "variable" ,
249250 Summary : v .Doc ,
250251 Langs : onlyGo ,
251- Syntax : syntax {Content : pkgsite .PrintType (pi .fset , v .Decl , toURL , topLevelDecls )},
252+ Syntax : syntax {Content : pkgsite .PrintType (pi .fset , v .Decl , link . toURL , topLevelDecls )},
252253 })
253254 }
254255
@@ -313,18 +314,84 @@ type linker struct {
313314 // Behavior is undefined when a single import has different names in
314315 // different files.
315316 imports map [string ]string
317+
318+ // idToAnchor is a map from an ID to the anchor URL for that ID.
319+ idToAnchor map [string ]string
316320}
317321
318- func newLinker (rawImports map [ string ] * packages. Package , importSyntax map [ string ] string ) * linker {
322+ func newLinker (pi pkgInfo ) * linker {
319323 imports := map [string ]string {}
320- for path , pkg := range rawImports {
324+ for path , pkg := range pi . pkg . Imports {
321325 name := pkg .Name
322- if rename := importSyntax [path ]; rename != "" {
326+ if rename := pi . importRenames [path ]; rename != "" {
323327 name = rename
324328 }
325329 imports [name ] = path
326330 }
327- return & linker {imports : imports }
331+
332+ idToAnchor := buildIDToAnchor (pi )
333+
334+ return & linker {imports : imports , idToAnchor : idToAnchor }
335+ }
336+
337+ // nonWordRegex is based on
338+ // https://github.com/googleapis/doc-templates/blob/70eba5908e7b9aef5525d0f1f24194ae750f267e/third_party/docfx/templates/devsite/common.js#L27-L30.
339+ var nonWordRegex = regexp .MustCompile ("\\ W" )
340+
341+ func buildIDToAnchor (pi pkgInfo ) map [string ]string {
342+ idToAnchor := map [string ]string {}
343+ idToAnchor [pi .doc .ImportPath ] = pi .doc .ImportPath
344+
345+ for _ , c := range pi .doc .Consts {
346+ commaID := strings .Join (c .Names , "," )
347+ uid := pi .doc .ImportPath + "." + commaID
348+ for _ , name := range c .Names {
349+ idToAnchor [name ] = uid
350+ }
351+ }
352+ for _ , v := range pi .doc .Vars {
353+ commaID := strings .Join (v .Names , "," )
354+ uid := pi .doc .ImportPath + "." + commaID
355+ for _ , name := range v .Names {
356+ idToAnchor [name ] = uid
357+ }
358+ }
359+ for _ , f := range pi .doc .Funcs {
360+ uid := pi .doc .ImportPath + "." + f .Name
361+ idToAnchor [f .Name ] = uid
362+ }
363+ for _ , t := range pi .doc .Types {
364+ uid := pi .doc .ImportPath + "." + t .Name
365+ idToAnchor [t .Name ] = uid
366+ for _ , c := range t .Consts {
367+ commaID := strings .Join (c .Names , "," )
368+ uid := pi .doc .ImportPath + "." + commaID
369+ for _ , name := range c .Names {
370+ idToAnchor [name ] = uid
371+ }
372+ }
373+ for _ , v := range t .Vars {
374+ commaID := strings .Join (v .Names , "," )
375+ uid := pi .doc .ImportPath + "." + commaID
376+ for _ , name := range v .Names {
377+ idToAnchor [name ] = uid
378+ }
379+ }
380+ for _ , f := range t .Funcs {
381+ uid := pi .doc .ImportPath + "." + t .Name + "." + f .Name
382+ idToAnchor [f .Name ] = uid
383+ }
384+ for _ , m := range t .Methods {
385+ uid := pi .doc .ImportPath + "." + t .Name + "." + m .Name
386+ idToAnchor [m .Name ] = uid
387+ }
388+ }
389+
390+ for id , anchor := range idToAnchor {
391+ idToAnchor [id ] = nonWordRegex .ReplaceAllString (anchor , "_" )
392+ }
393+
394+ return idToAnchor
328395}
329396
330397func (l * linker ) linkify (s string ) string {
@@ -342,11 +409,11 @@ func (l *linker) linkify(s string) string {
342409 // If s is not exported, it's probably a builtin.
343410 if ! token .IsExported (s ) {
344411 if doc .IsPredeclared (s ) {
345- return href (toURL ("builtin" , s ), s )
412+ return href (l . toURL ("builtin" , s ), s )
346413 }
347414 return fmt .Sprintf ("%s%s" , prefix , s )
348415 }
349- return fmt .Sprintf ("%s%s" , prefix , href (toURL ("" , s ), s ))
416+ return fmt .Sprintf ("%s%s" , prefix , href (l . toURL ("" , s ), s ))
350417 }
351418 // Otherwise, it's in another package.
352419 split := strings .Split (s , "." )
@@ -362,14 +429,17 @@ func (l *linker) linkify(s string) string {
362429 return fmt .Sprintf ("%s%s" , prefix , s )
363430 }
364431 name := split [1 ]
365- return fmt .Sprintf ("%s%s.%s" , prefix , href (toURL (pkgPath , "" ), pkg ), href (toURL (pkgPath , name ), name ))
432+ return fmt .Sprintf ("%s%s.%s" , prefix , href (l . toURL (pkgPath , "" ), pkg ), href (l . toURL (pkgPath , name ), name ))
366433}
367434
368435// TODO: link to the right baseURL, with the right module name and version
369436// pattern.
370- func toURL (pkg , name string ) string {
437+ func ( l * linker ) toURL (pkg , name string ) string {
371438 if pkg == "" {
372- return fmt .Sprintf ("#%s" , strings .ToLower (name ))
439+ if anchor := l .idToAnchor [name ]; anchor != "" {
440+ name = anchor
441+ }
442+ return fmt .Sprintf ("#%s" , name )
373443 }
374444 baseURL := "https://pkg.go.dev"
375445 if name == "" {
0 commit comments