@@ -315,74 +315,93 @@ type linker struct {
315315 // different files.
316316 imports map [string ]string
317317
318- // idToAnchor is a map from an ID to the anchor URL for that ID.
319- idToAnchor map [string ]string
318+ // idToAnchor is a map from package path to a map from ID to the anchor for
319+ // that ID.
320+ idToAnchor map [string ]map [string ]string
321+
322+ // sameDomainModules is a map from package path to module for every imported
323+ // package that should cross link on the same domain.
324+ sameDomainModules map [string ]* packages.Module
320325}
321326
322327func newLinker (pi pkgInfo ) * linker {
328+ sameDomainPrefixes := []string {"cloud.google.com/go" }
329+
323330 imports := map [string ]string {}
331+ sameDomainModules := map [string ]* packages.Module {}
332+ idToAnchor := map [string ]map [string ]string {}
333+
324334 for path , pkg := range pi .pkg .Imports {
325335 name := pkg .Name
326336 if rename := pi .importRenames [path ]; rename != "" {
327337 name = rename
328338 }
329339 imports [name ] = path
340+
341+ // TODO: Consider documenting internal packages so we don't have to link
342+ // out.
343+ if pkg .Module != nil && hasPrefix (pkg .PkgPath , sameDomainPrefixes ) && ! strings .Contains (pkg .PkgPath , "internal" ) {
344+ sameDomainModules [path ] = pkg .Module
345+
346+ docPkg , _ := doc .NewFromFiles (pkg .Fset , pkg .Syntax , path )
347+ idToAnchor [path ] = buildIDToAnchor (docPkg )
348+ }
330349 }
331350
332- idToAnchor : = buildIDToAnchor (pi )
351+ idToAnchor [ "" ] = buildIDToAnchor (pi . doc )
333352
334- return & linker {imports : imports , idToAnchor : idToAnchor }
353+ return & linker {imports : imports , idToAnchor : idToAnchor , sameDomainModules : sameDomainModules }
335354}
336355
337356// nonWordRegex is based on
338357// https://github.com/googleapis/doc-templates/blob/70eba5908e7b9aef5525d0f1f24194ae750f267e/third_party/docfx/templates/devsite/common.js#L27-L30.
339358var nonWordRegex = regexp .MustCompile ("\\ W" )
340359
341- func buildIDToAnchor (pi pkgInfo ) map [string ]string {
360+ func buildIDToAnchor (pkg * doc. Package ) map [string ]string {
342361 idToAnchor := map [string ]string {}
343- idToAnchor [pi . doc . ImportPath ] = pi . doc .ImportPath
362+ idToAnchor [pkg . ImportPath ] = pkg .ImportPath
344363
345- for _ , c := range pi . doc .Consts {
364+ for _ , c := range pkg .Consts {
346365 commaID := strings .Join (c .Names , "," )
347- uid := pi . doc .ImportPath + "." + commaID
366+ uid := pkg .ImportPath + "." + commaID
348367 for _ , name := range c .Names {
349368 idToAnchor [name ] = uid
350369 }
351370 }
352- for _ , v := range pi . doc .Vars {
371+ for _ , v := range pkg .Vars {
353372 commaID := strings .Join (v .Names , "," )
354- uid := pi . doc .ImportPath + "." + commaID
373+ uid := pkg .ImportPath + "." + commaID
355374 for _ , name := range v .Names {
356375 idToAnchor [name ] = uid
357376 }
358377 }
359- for _ , f := range pi . doc .Funcs {
360- uid := pi . doc .ImportPath + "." + f .Name
378+ for _ , f := range pkg .Funcs {
379+ uid := pkg .ImportPath + "." + f .Name
361380 idToAnchor [f .Name ] = uid
362381 }
363- for _ , t := range pi . doc .Types {
364- uid := pi . doc .ImportPath + "." + t .Name
382+ for _ , t := range pkg .Types {
383+ uid := pkg .ImportPath + "." + t .Name
365384 idToAnchor [t .Name ] = uid
366385 for _ , c := range t .Consts {
367386 commaID := strings .Join (c .Names , "," )
368- uid := pi . doc .ImportPath + "." + commaID
387+ uid := pkg .ImportPath + "." + commaID
369388 for _ , name := range c .Names {
370389 idToAnchor [name ] = uid
371390 }
372391 }
373392 for _ , v := range t .Vars {
374393 commaID := strings .Join (v .Names , "," )
375- uid := pi . doc .ImportPath + "." + commaID
394+ uid := pkg .ImportPath + "." + commaID
376395 for _ , name := range v .Names {
377396 idToAnchor [name ] = uid
378397 }
379398 }
380399 for _ , f := range t .Funcs {
381- uid := pi . doc .ImportPath + "." + t .Name + "." + f .Name
400+ uid := pkg .ImportPath + "." + t .Name + "." + f .Name
382401 idToAnchor [f .Name ] = uid
383402 }
384403 for _ , m := range t .Methods {
385- uid := pi . doc .ImportPath + "." + t .Name + "." + m .Name
404+ uid := pkg .ImportPath + "." + t .Name + "." + m .Name
386405 idToAnchor [m .Name ] = uid
387406 }
388407 }
@@ -436,11 +455,20 @@ func (l *linker) linkify(s string) string {
436455// pattern.
437456func (l * linker ) toURL (pkg , name string ) string {
438457 if pkg == "" {
439- if anchor := l .idToAnchor [name ]; anchor != "" {
458+ if anchor := l .idToAnchor ["" ][ name ]; anchor != "" {
440459 name = anchor
441460 }
442461 return fmt .Sprintf ("#%s" , name )
443462 }
463+ if mod , ok := l .sameDomainModules [pkg ]; ok {
464+ pkgRemainder := pkg [len (mod .Path )+ 1 :] // +1 to skip slash.
465+ // Note: we always link to latest. One day, we'll link to mod.Version.
466+ baseURL := fmt .Sprintf ("/go/docs/reference/%v/latest/%v" , mod .Path , pkgRemainder )
467+ if anchor := l.idToAnchor [pkg ][name ]; anchor != "" {
468+ return fmt .Sprintf ("%s#%s" , baseURL , anchor )
469+ }
470+ return baseURL
471+ }
444472 baseURL := "https://pkg.go.dev"
445473 if name == "" {
446474 return fmt .Sprintf ("%s/%s" , baseURL , pkg )
@@ -558,7 +586,7 @@ type pkgInfo struct {
558586
559587func loadPackages (glob , workingDir string ) ([]pkgInfo , error ) {
560588 config := & packages.Config {
561- Mode : packages .NeedName | packages .NeedSyntax | packages .NeedTypes | packages .NeedTypesInfo | packages .NeedModule | packages .NeedImports ,
589+ Mode : packages .NeedName | packages .NeedSyntax | packages .NeedTypes | packages .NeedTypesInfo | packages .NeedModule | packages .NeedImports | packages . NeedDeps ,
562590 Tests : true ,
563591 Dir : workingDir ,
564592 }
@@ -594,7 +622,7 @@ func loadPackages(glob, workingDir string) ([]pkgInfo, error) {
594622 }
595623 if strings .Contains (id , "_test" ) {
596624 id = id [0 :strings .Index (id , "_test [" )]
597- } else {
625+ } else if pkg . Module != nil {
598626 idToPkg [pkg .PkgPath ] = pkg
599627 pkgNames = append (pkgNames , pkg .PkgPath )
600628 // The test package doesn't have Module set.
@@ -674,3 +702,12 @@ func loadPackages(glob, workingDir string) ([]pkgInfo, error) {
674702
675703 return result , nil
676704}
705+
706+ func hasPrefix (s string , prefixes []string ) bool {
707+ for _ , prefix := range prefixes {
708+ if strings .HasPrefix (s , prefix ) {
709+ return true
710+ }
711+ }
712+ return false
713+ }
0 commit comments