@@ -26,6 +26,7 @@ import (
2626 "slices"
2727 "strings"
2828
29+ "github.com/bufbuild/buf/private/bufpkg/bufmodule"
2930 "github.com/bufbuild/protocompile/experimental/ast"
3031 "github.com/bufbuild/protocompile/experimental/ast/predeclared"
3132 "github.com/bufbuild/protocompile/experimental/ir"
@@ -190,7 +191,6 @@ func (s *symbol) LogValue() slog.Value {
190191//
191192// Returns the empty string if no docs are available.
192193func (s * symbol ) FormatDocs () string {
193- var def ast.DeclDef
194194 switch s .kind .(type ) {
195195 case * imported :
196196 imported , _ := s .kind .(* imported )
@@ -257,20 +257,21 @@ func (s *symbol) FormatDocs() string {
257257 builtin , _ := s .kind .(* builtin )
258258 comments , ok := builtinDocs [builtin .predeclared .String ()]
259259 if ok {
260+ comments = append (
261+ comments ,
262+ "" ,
263+ fmt .Sprintf (
264+ "`%s` is a Protobuf builtin. [Learn more on protobuf.com.](https://protobuf.com/docs/language-spec#field-types)" ,
265+ builtin .predeclared ,
266+ ),
267+ )
260268 return strings .Join (comments , "\n " )
261269 }
262270 return ""
263- case * referenceable :
264- referenceable , _ := s .kind .(* referenceable )
265- def = referenceable .ast
266- case * static :
267- static , _ := s .kind .(* static )
268- def = static .ast
269- case * reference :
270- reference , _ := s .kind .(* reference )
271- def = reference .def
271+ case * referenceable , * static , * reference :
272+ return s .getDocsFromComments ()
272273 }
273- return getCommentsFromDef ( def )
274+ return ""
274275}
275276
276277// GetSymbolInformation returns the protocol symbol information for the symbol.
@@ -343,10 +344,27 @@ func protowireTypeForPredeclared(name predeclared.Name) protowire.Type {
343344 return protowire .BytesType
344345}
345346
346- func getCommentsFromDef (def ast.DeclDef ) string {
347+ // getDocsFromComments is a helper function that gets the doc string from the comments from
348+ // the definition AST, if available.
349+ // This helper function expects that imports, tags, and predeclared (builtin) types are
350+ // already handled, since those types currently do not get docs from their comments.
351+ func (s * symbol ) getDocsFromComments () string {
352+ var def ast.DeclDef
353+ switch s .kind .(type ) {
354+ case * referenceable :
355+ referenceable , _ := s .kind .(* referenceable )
356+ def = referenceable .ast
357+ case * static :
358+ static , _ := s .kind .(* static )
359+ def = static .ast
360+ case * reference :
361+ reference , _ := s .kind .(* reference )
362+ def = reference .def
363+ }
347364 if def .IsZero () {
348365 return ""
349366 }
367+
350368 var comments []string
351369 // We drop the other side of "Around" because we only care about the beginning -- we're
352370 // traversing backwards for leading comemnts only.
@@ -365,7 +383,51 @@ func getCommentsFromDef(def ast.DeclDef) string {
365383 }
366384 // Reverse the list and return joined.
367385 slices .Reverse (comments )
368- return strings .Join (comments , "" )
386+
387+ var docs strings.Builder
388+ for _ , comment := range comments {
389+ docs .WriteString (comment )
390+ }
391+
392+ // If the file is a remote dependency, link to BSR docs.
393+ if s .def .file != nil && ! s .def .file .IsLocal () {
394+ // In the BSR, messages, enums, and service definitions support anchor tags in the link.
395+ // Otherwise, we use the anchor for the parent type.
396+ var hasAnchor , isExtension bool
397+ switch def .Classify () {
398+ case ast .DefKindMessage , ast .DefKindEnum , ast .DefKindService :
399+ hasAnchor = true
400+ case ast .DefKindExtend :
401+ isExtension = ! def .AsExtend ().Extendee .IsZero ()
402+ hasAnchor = isExtension
403+ }
404+
405+ var bsrHost , bsrAnchor string
406+ if s .def .file .IsWKT () {
407+ bsrHost = "buf.build/protocolbuffers/wellknowntypes"
408+ } else if fileInfo , ok := s .def .file .objectInfo .(bufmodule.FileInfo ); ok {
409+ bsrHost = fileInfo .Module ().FullName ().String ()
410+ }
411+ defFullName := s .def .ir .FullName ()
412+ if ! hasAnchor {
413+ defFullName = defFullName .Parent ()
414+ }
415+ bsrAnchor = string (defFullName )
416+ // For extensions, we use the anchor for the extensions section in the BSR docs.
417+ if isExtension {
418+ bsrAnchor = "extensions"
419+ }
420+ if bsrHost != "" {
421+ docs .WriteString (fmt .Sprintf (
422+ "\n [`%s` on the Buf Schema Registry](https://%s/docs/main:%s#%s)" ,
423+ defFullName ,
424+ bsrHost ,
425+ s .def .file .ir .Package (),
426+ bsrAnchor ,
427+ ))
428+ }
429+ }
430+ return docs .String ()
369431}
370432
371433// commentToMarkdown processes comment strings and formats them for markdown display.
0 commit comments